『Python数値計算ノート』ではアフィリエイトプログラムを利用して商品を紹介しています。

[Matplotlib] 平面と法線ベクトル

平面の方程式

この記事では三次元空間内の 平面 を表示するコードを掲載しています。

平面と法線ベクトル

固定点 $A(x_0,y_0,z_0)$ と平面に垂直なベクトル $\vec{n}=(p,q,r)$ が与えられたとします。
このようなベクトルを平面の 法線ベクトル とよびます。
平面上の点を $P(x,y,z)$ で表すと、ベクトル $\vec{AP}$ は法線ベクトルに垂直です。
 
Python 法線と平面
 
つまり $\vec{AP}$ と $\vec{n}$ の内積は $0$ です:
 \[\vec{n}\cdot\vec{AP}=0\tag{1}\]
具体的に成分で書き表すと
 \[\begin{bmatrix}p\\q\\r\end{bmatrix}\cdot\begin{bmatrix}x-x_0\\y-y_0\\z-z_0\end{bmatrix}=0\tag{2}\]
となります。よって、平面の方程式は
 \[p(x-x_0)+q(y-y_0)+r(z-z_0)=0\tag{3}\]
で与えられることになります。
 
Matplotlib を使って平面を描いてみましょう。以下に定義する nvector_plane() は法線ベクトルと任意の点を与えて平面をプロットする関数です。

# PYTHON_MATPLOTLIB_PLANE_01-1

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

# 法線ベクトルを指定して平面をプロットする関数
# axes:サブプロット
# vector:法線ベクトル
# point:平面上の点
# xrange,yrange,zrange:x軸,y軸,z軸の範囲
# loc:法線ベクトルの始点(デフォルトは原点)
# vcolor:法線ベクトルの色
# pcolor,alpha:平面の色,透明度

def nvector_plane(axes, vector, point,
                  xrange, yrange, zrange,
                  loc = [0, 0, 0],
                  vcolor="red", pcolor="blue", alpha=0.5):

    # 軸ラベルの設定
    axes.set_xlabel("x", fontsize = 16)
    axes.set_ylabel("y", fontsize = 16)
    axes.set_zlabel("z", fontsize = 16)

    # 軸範囲の設定
    axes.set_xlim(xrange[0], xrange[1])
    axes.set_ylim(yrange[0], yrange[1])
    axes.set_zlim(zrange[0], zrange[1])

    # 格子点の作成
    x = np.arange(xrange[0], xrange[1], 0.2)
    y = np.arange(yrange[0], yrange[1], 0.2)
    xx, yy = np.meshgrid(x, y)

    # 平面の方程式
    zz = point[2] - (vector[0]*(xx-point[0])+vector[1]*(yy-point[1])) / vector[2]

    # 平面をプロット
    ax.plot_surface(xx, yy, zz, color=pcolor, alpha=alpha)

    # 法線ベクトルをプロット
    axes.quiver(loc[0], loc[1], loc[2],
                vector[0], vector[1], vector[2],
                color = vcolor, length = 1, arrow_length_ratio = 0.2)

点 $(0,0,0)$ を通り、法線ベクトル $\vec{n}=(2,5,3)$ に垂直な平面を表示させてみましょう。

# PYTHON_MATPLOTLIB_PLANE_01-2

# FigureとAxes
fig = plt.figure(figsize = (8, 6))
ax = fig.add_subplot(111, projection='3d')

# 平面上の点
p = (0, 0, 0)

# 法線ベクトル
v = (2, 5, 3)

# 法線ベクトルの始点
loc = (0, 0, 0)

# x,y,z軸の範囲
xr = [-5, 5]
yr = [-5, 5]
zr = [-5, 5]

# 法線ベクトルと平面をプロット
nvector_plane(ax, loc, v, p, xr, yr, zr)

# 画像の保存
plt.savefig("normal_vector_plane.png", bbox_inches = "tight")

Python-Matplotlib 法線ベクトルによって定義される平面の描画
 
$3$ 点 $A,\ B,\ C$ が指定されると 1 つの平面が定まります。
この平面の法線ベクトルは $\vec{AB}$ と $\vec{AC}$ に垂直なので、
 \[\vec{n}=\vec{AB}\times \vec{AC}\tag{4}\]
で与えられます。nvector_plane() を再利用して、$3$ 点を通る平面を描画する関数を定義できます。

# PYTHON_MATPLOTLIB_PLANE_01-3

# 3点を通る平面をプロットする関数
# axes:サブプロット
# point:平面上の点
# xrange,yrange,zrange:x軸,y軸,z軸の範囲
# loc:法線ベクトルの始点(デフォルトは原点)
# vcolor:法線ベクトルの色
# pcolor,alpha:平面の色,透明度

def point_plane(axes, p0, p1, p2,
                xrange, yrange, zrange, 
                loc = [0, 0, 0],
                vcolor="red", pcolor="blue", alpha=0.5):

    u = p1 - p0
    v = p2 - p0
    w = np.cross(u, v)
    w = 0.5 * np.abs(xrange[0]-yrange[1]) * w / np.sqrt(np.sum(w**2))
    
    nvector_plane(axes, w, p0,
                  xrange, yrange, zrange,
                  loc=loc, vcolor=vcolor, pcolor=pcolor, alpha=alpha)

 
点 $(3,1,1),\ (0,2,0),\ (-1, -1, 3)$ の $3$ 点を通る平面をプロットします。

# PYTHON_MATPLOTLIB_PLANE_01-4

# FigureとAxes
fig = plt.figure(figsize = (8, 6))
ax = fig.add_subplot(111, projection='3d')

# x,y,z軸の範囲
xr = [-5, 5]
yr = [-5, 5]
zr = [-5, 5]

# 平面内の3点を指定
p0 = np.array([3, 1, 1])
p1 = np.array([0, 2, 0])
p2 = np.array([-1, -1, 3])

# 3点の重心
g = (p0 + p1 + p2) / 3

# 3点を通る平面をプロット
point_plane(ax, p0, p1, p2, xr, yr, zr,loc=g)

Python 3点を通る平面と法線

一般的な平面方程式

(3) で表された方程式
 \[p(x-x_0)+q(y-y_0)+r(z-z_0)=0\tag{3}\]
の定数部分を $s$ にまとめてしまうと、一般的な 平面方程式 が得られます。
 \[px+qy+rz+s=0\tag{5}\]
式変形によって点 $A(x_0,y_0,z_0)$ の情報は失われましたが、係数 $p,q,r$ は残っているので法線ベクトルの情報は保持されています。さらに、$z=f(x,y)$ の形に変形すると
 \[z=-\frac{px+qy+s}{r}\tag{6}\]
となります。(5) を使って平面を描く関数を定義してみます。

# PYTHON_MATPLOTLIB_PLANE_02-1

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

# px+qy+rz+s=0をプロットする関数
# axes:サブプロット
# param:p,q,r,sのリストまたはタプルなど
# xrange,yrange,zrange:x軸,y軸,z軸の範囲
# pcolor,alpha:平面の色,透明度

def plot_plane(axes, param, xrange, yrange, zrange,
               pcolor="blue", alpha=0.5):

    # 軸ラベルの設定
    axes.set_xlabel("x", fontsize = 16)
    axes.set_ylabel("y", fontsize = 16)
    axes.set_zlabel("z", fontsize = 16)

    # 軸範囲の設定
    axes.set_xlim(xrange[0], xrange[1])
    axes.set_ylim(yrange[0], yrange[1])
    axes.set_zlim(zrange[0], zrange[1])

    # 格子点の作成
    x = np.arange(xrange[0], xrange[1], 0.2)
    y = np.arange(yrange[0], yrange[1], 0.2)
    xx, yy = np.meshgrid(x, y)

    # 平面の方程式
    zz = -(param[0]*xx + param[1]*yy + param[3]) / param[2]

    # 平面をプロット
    ax.plot_surface(xx, yy, zz, color=pcolor, alpha=alpha)

plot_plane() を使って、$2x-y+3z=0$ で表される平面をプロットします。

# PYTHON_MATPLOTLIB_PLANE_02-2

# FigureとAxes
fig = plt.figure(figsize = (8, 6))
ax = fig.add_subplot(111, projection='3d')

# x,y,z軸の範囲
xr = [-5, 5]
yr = [-5, 5]
zr = [-5, 5]

# p,q,r,sを設定
param = [2, -1, 3, 0]

# 平面2x-y+3z=0をプロット
plot_plane(ax, param, xr, yr, zr)

# 画像の保存
plt.savefig("plot_plane.png", bbox_inches = "tight")

Python-Matplotlib 一般的な方程式による平面のプロット

基底ベクトルで表される平面

三次元空間における平面は 2 つの独立な三次元ベクトル (基底ベクトル) の線形結合によって表すことができます。
 \[\begin{bmatrix}x\\y\\z\end{bmatrix}=s\begin{bmatrix}a\\b\\c\end{bmatrix}+t\begin{bmatrix}d\\e\\f\end{bmatrix}\tag{7}\]
パラメータ (媒介変数) で表される方程式なので、Python に実装するときにはパラメータの格子点データをつくります。

# PYTHON_MATPLOTLIB_PLANE_03-1

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

# 基底ベクトルを指定して平面をプロットする関数
# axes:サブプロット
# v,w:基底ベクトル
# xrange,yrange,zrange:x軸,y軸,z軸の範囲
# vcolor,wcolor:基底ベクトルの色
# pcolor,alpha:平面の色,透明度

def bvector_plane(axes, v, w, xrange, yrange, zrange,
                  srange, trange,
                  vcolor="red", wcolor="blue",
                  pcolor="green", alpha=0.2):

    # 軸ラベルの設定
    axes.set_xlabel("x", fontsize = 16)
    axes.set_ylabel("y", fontsize = 16)
    axes.set_zlabel("z", fontsize = 16)

    # 軸範囲の設定
    axes.set_xlim(xrange[0], xrange[1])
    axes.set_ylim(yrange[0], yrange[1])
    axes.set_zlim(zrange[0], zrange[1])

    # 格子点の作成
    s = np.arange(srange[0], srange[1], 0.2)
    t = np.arange(trange[0], trange[1], 0.2)
    ss, tt = np.meshgrid(s, t)

    # 基底ベクトルの線形結合
    xx = v[0]*ss + w[0]*tt
    yy = v[1]*ss + w[1]*tt
    zz = v[2]*ss + w[2]*tt

    # 平面をプロット
    ax.plot_surface(xx, yy, zz, color=pcolor, alpha=alpha)

    # 基底ベクトルをプロット
    axes.quiver(0, 0, 0, v[0], v[1], v[2],
                color=vcolor, length=1, arrow_length_ratio=0.2)
    
    axes.quiver(0, 0, 0, w[0], w[1], w[2],
                color=wcolor, length=1, arrow_length_ratio=0.2)

bvector_plane() を使って基底ベクトル $\vec{v}=(1, 1, 1)$ と $\vec{w}=(2, -1, 4)$ が張る平面をプロットしてみます。

# PYTHON_MATPLOTLIB_PLANE_03-2

# FigureとAxes
fig = plt.figure(figsize = (8, 6.5))
ax = fig.add_subplot(111, projection='3d')

# 基底ベクトル
v = [1,  1, 1]
w = [2, -1, 4]

# パラメータの範囲
srange = [-2, 2]
trange = [-2, 2]

# 軸範囲の設定
xrange = [-5, 5]
yrange = [-5, 5]
zrange = [-5, 5]

# 基底ベクトルと平面をプロット
bvector_plane(ax, v, w, xrange, yrange, zrange, srange, trange)

Matplotlib 基底ベクトルによる平面のプロット

コメント