PR

Matplotlibで3次元空間に円を描画、透過、境界を描画、線を引く

Python便利帳
Sponsored

この記事では、PythonのMatplotlibを使用して3次元空間に球や面、線などを描画する方法について解説する。なお、ここで作成した画像の一部は

多重積分を極座標変換して簡略化する(M次元単位球の表面積も導出)
多重積分の独立した変数が動径としてまとめられるとき、変数を極座標に変換することで、計算を簡略化することができます。具体的には、複数の変数による積分が、1変数の積分と単位球の表面積の積に変換できます。この記事では、変数を極座標に変換する方法と面素を用いて変数をまとめる方法を解説し、最後に極座標変換の性質を応用して、多次元単位球の表面積を導出します。

において利用されている。

球体の描画

基本

球を描画するには、極座標の考え方を利用する必要がある。直交座標 \(x,y,z\) を極座標 \(r,\theta_1,\theta_2\) に変換するときには

$$x_1 = r\cos{\theta_2}\sin{\theta_1}$$

$$x_2 = r\sin{\theta_2}\sin{\theta_1}$$

$$x_3 = r\cos{\theta_1}$$

の対応関係を用いる。

import numpy as np
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

r = 3 # 半径を指定
theta_1_0 = np.linspace(0, np.pi/2, 100) # θ_1は[0,π/2]の値をとる
theta_2_0 = np.linspace(0, np.pi/2, 100) # θ_2は[0,π/2]の値をとる
theta_1, theta_2 = np.meshgrid(theta_1_0, theta_2_0) # 2次元配列に変換
x = np.cos(theta_2)*np.sin(theta_1) * r # xの極座標表示
y = np.sin(theta_2)*np.sin(theta_1) * r # yの極座標表示
z = np.cos(theta_1) * r # zの極座標表示

fig = plt.figure() # 描画領域を作成
ax = fig.add_subplot(111, projection="3d") # 3Dの軸を作成
ax.plot_surface(x,y,z) # 球を3次元空間に表示
plt.xlim([0,3])
plt.ylim([3,0]) # Y軸の表示は初期値とは逆にする
ax.set_zlim([0,3])
plt.xlabel("X")
plt.ylabel("Y")
ax.set_zlabel("Z")
plt.show()

np.linspaceで \([0,\frac{\pi}{2}]\) の値をとる (\theta_1,\theta_2\) の値を100個ずつ生成する。そして、球を3次元空間に描画するax.plot_surfaceは引数に2次元配列をとるので、np.meshgridを用いて \(\theta_1,\theta_2\) 配列の形状を変換する。これを用いて \(x,y,z\) を計算すると、 \(x,y,z\) も2次元配列として得られる。

最後に描画領域と3D軸を作成してax.plot_surfaceで描画すると、上図のような半径 \(r\) の \(\frac{1}{8}\) 円が得られる。

np.meshgridについての詳細は以下を参照のこと。

matplotlibで3次元空間に2次元ヒストグラムを表示する方法
この記事では、PythonのMatplotlibを用いて、XとYの2種類の値をとる2次元変数(X, Y)についての2次元...

以下、上記のコードと異なる部分のみを掲載する。

透過

ax.plot_surface(x,y,z, alpha=0.2) # 球を3次元空間に表示

ax.plot_surfaceを用いて描画する際に、alpha値を指定することで描画図形を透過することができる。このalpha値は0~1の間で指定することができ、1に近い値をとるほど濃く描画される。

領域と境界の描画

続いて、 \(r,\theta_1,\theta_2\) をそれぞれ \([r,r+dr], [\phi_1,\phi_1+d\phi_1], [\phi_2,\phi_2+d\phi_2]\) の範囲で動かしたときに得られる領域を描画する。このコードの例では、具体的な値として \(r=3, dr=0.3, \phi_1=\phi_2=\frac{\pi}{12}, d\phi_1=d\phi_2=\frac{\pi}{12}\) を代入している。

phi_1 = np.linspace(np.pi/12*1, np.pi/12*2, 50) # φ_1は[π/12,π/6]の値をとる
phi_2 = np.linspace(np.pi/12*1, np.pi/12*2, 50) # φ_2は[π/12,π/6]の値をとる
phi_1_0, phi_2_0 = np.meshgrid(phi_1, phi_2) # 2次元配列に変換
x_p = np.cos(phi_2_0)*np.sin(phi_1_0) * r # xの極座標表示
y_p = np.sin(phi_2_0)*np.sin(phi_1_0) * r # yの極座標表示
z_p = np.cos(phi_1_0) * r # zの極座標表示

dr = 0.3
x_dp = np.cos(phi_2_0)*np.sin(phi_1_0) * (r + dr) # xの極座標表示
y_dp = np.sin(phi_2_0)*np.sin(phi_1_0) * (r + dr) # yの極座標表示
z_dp = np.cos(phi_1_0) * (r + dr) # zの極座標表示

r_b = np.linspace(r, r+dr, 50) # 半径を[r,r+dr]まで動かす

phi_12_1_1, r_b_12_1_1 = np.meshgrid(phi_1, r_b)
x_12_1_1 = np.cos(np.pi/12*1)*np.sin(phi_12_1_1)*r_b_12_1_1 # φ_2をπ/12で固定
y_12_1_1 = np.sin(np.pi/12*1)*np.sin(phi_12_1_1)*r_b_12_1_1 # φ_2をπ/12で固定
z_12_1_1 = np.cos(phi_12_1_1)*r_b_12_1_1 # φ_2をπ/12で固定

phi_12_1_2, r_b_12_1_2 = np.meshgrid(phi_2, r_b)
x_12_1_2 = np.cos(phi_12_1_2)*np.sin(np.pi/12*1)*r_b_12_1_2 # φ_1をπ/12で固定
y_12_1_2 = np.sin(phi_12_1_2)*np.sin(np.pi/12*1)*r_b_12_1_2 # φ_1をπ/12で固定
z_12_1_2 = np.cos(np.pi/12*1)*r_b_12_1_2 # φ_1をπ/12で固定

phi_12_2_1, r_b_12_2_1 = np.meshgrid(phi_1, r_b)
x_12_2_1 = np.cos(np.pi/12*2)*np.sin(phi_12_2_1)*r_b_12_2_1 # φ_2をπ/6で固定
y_12_2_1 = np.sin(np.pi/12*2)*np.sin(phi_12_2_1)*r_b_12_2_1 # φ_2をπ/6で固定
z_12_2_1 = np.cos(phi_12_2_1)*r_b_12_2_1 # φ_2をπ/6で固定

phi_12_2_2, r_b_12_2_2 = np.meshgrid(phi_2, r_b)
x_12_2_2 = np.cos(phi_12_2_2)*np.sin(np.pi/12*2)*r_b_12_2_2 # φ_1をπ/6で固定
y_12_2_2 = np.sin(phi_12_2_2)*np.sin(np.pi/12*2)*r_b_12_2_2 # φ_1をπ/6で固定
z_12_2_2 = np.cos(np.pi/12*2)*r_b_12_2_2 # φ_1をπ/6で固定

ax.plot_surface(x_p,y_p,z_p, color="g") # 半径rの球の一部を3次元空間に表示
ax.plot_surface(x_dp,y_dp,z_dp, color="g") # 半径r+drの球の一部を3次元空間に表示
ax.plot_surface(x_12_1_1,y_12_1_1,z_12_1_1, color="g") # 境界を3次元空間に表示
ax.plot_surface(x_12_1_2,y_12_1_2,z_12_1_2, color="g") # 境界を3次元空間に表示
ax.plot_surface(x_12_2_1,y_12_2_1,z_12_2_1, color="g") # 境界を3次元空間に表示
ax.plot_surface(x_12_2_2,y_12_2_2,z_12_2_2, color="g") # 境界を3次元空間に表示

https://ushitora.net/wp-content/uploads/2019/02/sphere3.png

np.linspaceで \([\frac{\pi}{12},\frac{\pi}{6}]\) の範囲を動く \(\phi_1, \phi_2\) を新たに作成する。コードの第1・第2ブロックでは、この \(\phi_1, \phi_2\) を用いて、半径が \(r=3\) のときと \(r+dr=3.3\) のときの球の一部を描画している。

第1・第2ブロックの内容のみ表示

あとはこの2曲面を底面とした場合の4つの側面を描画すればよいのであるが、そのためには再びnp.linspaceを用いて、 \([3.0,3.3]\) の区間を動く \(r_b\) を作成する。これと \(\phi_1, \phi_2\) の組み合わせで境界面を描画する。第4ブロックでは \(\phi_2\) の値を \(\frac{\pi}{12}\) に固定し、 \(\phi_1\) と \(r_b\) のみを動かすことで境界を作成した。同様