問題文
$xyz$ 空間において、点 $(1,0,1)$ と点 $(1,0,2)$ を結ぶ線分を $\ell$ とし、 $\ell$ を $z$ 軸のまわりに1回転してできる図形を $A$ とする。 $A$ を $x$ 軸のまわりに1回転してできる立体の体積を求めよ。(2007 東北大学 理系第5問)
$$6\pi$$
※ この答えは学校側が公表したものではありません。個人が作成した非公式の答えです。
コード
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as ani
from matplotlib.gridspec import GridSpec
from matplotlib.colors import ListedColormap
from scipy.spatial.transform import Rotation
from mpl_toolkits.mplot3d.art3d import Line3D
from matplotlib.patches import FancyArrowPatch
from mpl_toolkits.mplot3d.proj3d import proj_transform
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
#紙の準備
fig = plt.figure()
fig.canvas.draw()
fig.suptitle("2007東北大数学 理系第5問",
color="0.7",ha="right",x=0.96,y=0.96)
gs = GridSpec(2,4,height_ratios=[3,2])
ax1 = fig.add_subplot(gs[0,:],projection='3d',computed_zorder=False)
ax1.view_init(35,-55)
ax1.set_xlim(-0.9,1.5)
ax1.set_ylim(-1.2,1.2)
ax1.set_zlim(-1.2,1.2)
ax1.set_box_aspect((1,1,1))
ax1.axis("off")
ax2 = fig.add_subplot(gs[1,1:])
ax2.set_xlim(0,8)
ax2.set_ylim(-3,0)
ax2.set_aspect("equal")
ax2.axis("off")
#回転体のための関数その1
n = 61
def make_rotxyzs(xyz,rotvec):
rotxyzs = []
for i in range(n):
rvi = rotvec*i/(n-1)
R = Rotation.from_rotvec(rvi).as_matrix()
rotxyz = R @ xyz
rotxyzs.append(rotxyz)
return rotxyzs
#回転体のための関数その2
col_r = (.5,1,1,.5)
col_u = (0,.5,.5,.1)
col_d = (0,0,1,.1)
def make_bands(rotxyzs,fc):
bands = []
for i,rotxyz in enumerate(rotxyzs[1:],1):
verts = []
for j in range(1,rotxyz.shape[1]):
vert = [rotxyzs[i-1][:,j-1],rotxyzs[i-1][:,j],rotxyz[:,j],rotxyz[:,j-1]]
verts.append(vert)
band = Poly3DCollection(verts,fc=fc)
bands.append(band)
return bands
#回転体を準備(リング)
x1 = [1,1]
y1 = [0,0]
z1 = [1,2]
l = Line3D(x1,y1,z1,color="b")
ax1.add_line(l)
xyz1 = np.array([x1,y1,z1])
rotvec1 = np.array([0,0,2*np.pi])
rot1xyzs = make_rotxyzs(xyz1,rotvec1)
bands1 = make_bands(rot1xyzs,col_r)
#回転体を準備(タイヤ)
theta = np.linspace(0,2*np.pi,n)
x2 = np.cos(theta)
y2 = np.sin(theta)
z2u = 2*np.ones_like(theta)
z2d = 1*np.ones_like(theta)
xyz2u = np.stack([x2,y2,z2u])
xyz2d = np.stack([x2,y2,z2d])
rotvec2 = np.array([2*np.pi,0,0])
rot2xyzus = np.array(make_rotxyzs(xyz2u,rotvec2))
rot2xyzds = np.array(make_rotxyzs(xyz2d,rotvec2))
bands2u = make_bands(rot2xyzus[:,:,:(n+1)//2],col_u)
bands2d = make_bands(rot2xyzds[:,:,:(n+1)//2],col_d)
#リングを回すための関数
def rotate_ring(k):
verts = []
for i in range(n-1):
vert = [rot2xyzds[k,:,i],rot2xyzus[k,:,i],rot2xyzus[k,:,i+1],rot2xyzds[k,:,i+1]]
verts.append(vert)
return verts
verts = rotate_ring(0)
ring = Poly3DCollection(verts,fc=col_r)
#輪切りを準備
x = np.linspace(-1,1,n)
r1 = np.sqrt(5-x**2)
r2 = np.sqrt(2-x**2)
cut_verts = []
for i in range(n):
cx = x[i]*np.ones_like(theta)
cy1 = r1[i]*np.cos(theta)
cz1 = r1[i]*np.sin(theta)
cy2 = r2[i]*np.cos(-theta)
cz2 = r2[i]*np.sin(-theta)
vert = [list(zip(cx,cy1,cz1))+list(zip(cx,cy2,cz2))]
cut_verts.append(vert)
cut = Poly3DCollection([],fc=col_r)
ax1.add_collection3d(cut)
#積分の計算を準備
mathdic={
"size" : 16,
"usetex" : True,
"clip_on" : True,
}
lines = [
"=\\pi\\int_{-1}^1(\\,R^2-r^2\\,)\\,dx",
"=\\pi\\int_{-1}^1\\Biggl\\{(\\,y^2+2^2\\,)-(\\,y^2+1^2\\,)\\Biggr\\}\\,dx",
"=\\pi\\int_{-1}^1 3\\,dx",
"=\\pi\\,\\Biggl[\\,3x\\,\\Biggr]_{-1}^1",
"=\\pi\\times3\\times2",
"=6\\,\\pi"
]
#数式を書き込んでいきます
colors = [(1,1,1,alpha) for alpha in np.arange(0,11)/10]
cmap = ListedColormap(colors)
Texts,Times1,Pcms,Times2 = [],[],[],[]
for i,line in enumerate(lines):
latex = "$\\displaystyle"+line+"$"
text = ax2.text(0,-1.5*(i+1)+0.63,latex,fontdict=mathdic)
Texts.append(text)
bbox = text.get_window_extent().transformed(ax2.transData.inverted())
time1 = int(bbox.x1*5+15)
Times1.append(time1)
X,Y = np.mgrid[-2:12:0.1,-1.5*(i+1):-1.5*i+0.1:1.5]
C = np.ones((len(X)-1,1))
C[0] = 0
pcm = ax2.pcolormesh(X,Y,C,cmap=cmap,zorder=10)
Pcms.append(pcm)
Times2 = np.insert(Times1,range(2,len(Times1)),20)
Times2 = np.cumsum(Times2)
#3D矢印を使うためのクラス
class Arrow3D(FancyArrowPatch):
def __init__(self, x, y, z, dx, dy, dz, *args, **kwargs):
super().__init__((0, 0), (0, 0), *args, **kwargs)
self._xyz = (x, y, z)
self._dxdydz = (dx, dy, dz)
def draw(self, renderer):
x1, y1, z1 = self._xyz
dx, dy, dz = self._dxdydz
x2, y2, z2 = (x1 + dx, y1 + dy, z1 + dz)
xs, ys, zs = proj_transform((x1, x2), (y1, y2), (z1, z2), self.axes.M)
self.set_positions((xs[0], ys[0]), (xs[1], ys[1]))
super().draw(renderer)
def do_3d_projection(self, renderer=None):
x1, y1, z1 = self._xyz
dx, dy, dz = self._dxdydz
x2, y2, z2 = (x1 + dx, y1 + dy, z1 + dz)
xs, ys, zs = proj_transform((x1, x2), (y1, y2), (z1, z2), self.axes.M)
self.set_positions((xs[0], ys[0]), (xs[1], ys[1]))
return np.min(zs)
#半径を示すための矢印を準備
arrow1 = Arrow3D(1,0,-.08,0,2,0,color="0.4")
arrow2 = Arrow3D(1,0,0.08,0,1,0,color="0.3")
arrows = [arrow1,arrow2]
for arrow in arrows:
arrow.set_mutation_scale(10)
arrow.set_arrowstyle("<|-|>")
ax1.add_artist(arrow)
text1 = ax1.text(1,1.2,-.4,"$R$",color="0.4")
text2 = ax1.text(1,0.2,0.4,"$r$",color="0.3")
text3 = fig.text(.295,.305,"$V$",fontdict=mathdic)
annotations = arrows+[text1,text2,text3]
for obj in annotations:
obj.set_alpha(0)
#下部の解説枠を準備
waku = fig.text(0.5,0.25,"棒があります",
size=14,color="steelblue",ha="center",fontfamily="HGSoeiKakupoptai",
bbox=dict(boxstyle="round,pad=1.5",lw=1.5,fc="aliceblue",ec="steelblue"))
#立体は枠からはみ出して
for obj in [l,ring,cut]+bands1+bands2u+bands2d:
obj.set_clip_on(False)
#回転その1
def rotation1(i):
ax1.add_collection3d(bands1[i])
l.set_data_3d(rot1xyzs[i+1])
#回転その2
def rotation2(i):
if i==0:
for obj in bands1:
obj.set_visible(False)
ax1.add_collection3d(ring)
ax1.add_collection3d(bands2u[i])
ax1.add_collection3d(bands2d[i])
verts = rotate_ring(i+1)
ring.set_verts(verts)
#イージング関数
def easing(x):
if x<0.0:
return 0
elif x<0.5:
return 2*x**2
elif x<1.0:
return 1-(-2*x+2)**2/2
else:
return 1
#単一行を徐々に見せていく関数
def appear_line(i,pcm):
C = np.ones(len(pcm.get_array()))
C[:2*i+1] = 0
C[2*i+1:2*i+20] = np.arange(0.05,1.00,0.05)
pcm.set_array(C)
#複数行を徐々に見せていく関数
def show_lines(i):
n = np.argmin(~(i<Times2))
Times3 = np.append(0,Times2)
k = i-Times3[n]
if n==0:
appear_line(k,Pcms[0])
elif n%2 == 1:
appear_line(k,Pcms[n//2+1])
else:
alpha = (k+1)/20
Texts[n//2-1].set_alpha(easing(1-1.5*alpha))
top = -1.5*((n//2-1)+easing(alpha))
ax2.set_ylim(top-3,top)
#セクションを判定する関数
def section(i):
n = np.argmin(~(i<endings[1:]))
k = i-endings[n]
T = list(periods.values())[n]
a = easing(k/(T-1))
key = list(periods.keys())[n]
return k,a,key
#台本
periods = {"P1":20,"R1":n-1,"A1":30,"R2":n-1,"A2":30,
"S1":n,"A3":30,"L1":Times2[-1],"P2":40}
endings = np.cumsum(list(periods.values()))
endings = np.append(0,endings)
#上演
def update(i):
k,a,key = section(i)
if "R1"==key:
rotation1(k)
elif "A1"==key:
l.set_alpha(1-a)
elif "R2"==key:
rotation2(k)
elif "A2"==key:
ring.set_alpha(col_r[3]*(1-a))
elif "S1"==key:
cut.set_verts(cut_verts[k])
elif "A3"==key:
for obj in annotations:
obj.set_alpha(a)
for obj in [waku,waku.get_bbox_patch()]:
obj.set_alpha(1-a)
elif "L1" == key:
show_lines(k)
if k==0:
if "R1"==key:
waku.set_text("棒を回します")
elif "A1"==key:
waku.set_text("リングができます")
elif "R2"==key:
waku.set_text("リングも回します")
elif "A2"==key:
waku.set_text("タイヤができました!")
elif "S1"==key:
waku.set_text("輪切りはこんな感じ♪")
mov = ani.FuncAnimation(fig,update,endings[-1],interval=100)
plt.show()