からっぽのしょこ

読んだら書く!書いたら読む!同じ事は二度調べ(たく)ない

【Python】5.1:ベクトルの線形従属の可視化【『スタンフォード線形代数入門』のノート】

はじめに

 『スタンフォード ベクトル・行列からはじめる最適化数学』の学習ノートです。
 「数式の行間埋め」や「Pythonを使っての再現」によって理解を目指します。本と一緒に読んでください。

 この記事は5.1節「線形従属」の内容です。
 線形従属なベクトル集合のグラフを作成します。

【前の内容】

www.anarchive-beta.com

【他の内容】

www.anarchive-beta.com

【今回の内容】

ベクトルの線形従属の可視化

 ベクトルの線形従属(linear dependent・linearly dependent vectors)をグラフで確認します。
 線形結合については「【Python】1.3:ベクトルの線形結合の可視化【『スタンフォード線形代数入門』のノート】 - からっぽのしょこ」を参照してください。

 利用するライブラリを読み込みます。

# 利用ライブラリ
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation


2次元の場合

 まずは、2次元空間(平面)上でベクトルの線形従属を可視化します。

 2次元ベクトルを指定します。

# ベクトルを指定
a1 = np.array([3.0, 2.0])

 ベクトル \mathbf{a}_1 = (a_{1,1}, a_{1,2})^{\top}a1として値を指定します。Pythonでは0からインデックスが割り当てられるので、 a_{1,1}の値はa1[0]に対応します。

 ベクトル \mathbf{a}_1と平行な直線(原点と点 \mathbf{a}_1を通る直線)の座標を計算します。

# 直線用の係数を作成
beta1_vals = np.linspace(start=-2.0, stop=2.0, num=101)
print(beta1_vals[:5])

# ベクトルa1と平行な直線の座標を計算
x1_vals = beta1_vals * a1[0]
x2_vals = beta1_vals * a1[1]
print(x1_vals[:5])
print(x1_vals[:5])
[-2.   -1.96 -1.92 -1.88 -1.84]
[-6.   -5.88 -5.76 -5.64 -5.52]
[-6.   -5.88 -5.76 -5.64 -5.52]

  \mathbf{a}_1を定数倍したベクトル \mathbf{b} = \beta_1 \mathbf{a}_1がとり得る値(座標)を計算します。
  \beta_1の値を作成してbeta1_valsとして、x軸の値 b_1 = \beta_1 a_{1,1}とy軸の値 b_2 = \beta_1 a_{1,2}を計算してx1_vals, x2_valsとします。

 2次元空間上に、ベクトル \mathbf{a}_1と直線のグラフを作成します。

# グラフサイズ用の値を設定
x_min = np.floor(x1_vals.min())
x_max =  np.ceil(x1_vals.max())
y_min = np.floor(x2_vals.min())
y_max =  np.ceil(x2_vals.max())

# 線形独立の2Dベクトルを作図
fig, ax = plt.subplots(figsize=(8, 6), facecolor='white')
ax.quiver(0, 0, *a1, 
          color='red', width=0.01, headwidth=5, headlength=5, 
          angles='xy', scale_units='xy', scale=1, 
          label='$a_1=('+', '.join(map(str, a1))+')$') # ベクトルa1
ax.plot(x1_vals, x2_vals, 
        label='$\\beta_1 a_1$', zorder=0) # a1と平行な直線
ax.set_xticks(ticks=np.arange(x_min, x_max+1))
ax.set_yticks(ticks=np.arange(y_min, y_max+1))
ax.set_xlabel('$x_1$')
ax.set_ylabel('$x_2$')
ax.grid()
fig.suptitle('linear independent', fontsize=20)
ax.legend()
ax.set_aspect('equal')
plt.show()

2次元空間における線形独立であるベクトル集合

 axes.quiver()で2次元ベクトルを描画します。第1・2引数に始点の座標、第3・4引数にベクトルのサイズ(移動量)を指定します。その他に、指定した値の通りに(調整せずに)矢印を描画するための設定をしています。
 配列a1の前に*を付けてアンパック(展開)して座標を指定します。始点は原点です。

 原点と点 \mathbf{a}_1を通る直線が、ベクトル \mathbf{a}_1と平行なのを確認できます。この直線上の点は、ベクトル \mathbf{a}_1の定数倍によって表現できます。

  \mathbf{a}_1, \mathbf{b}が線形従属になるベクトルを作成します。

# x軸の値を指定
b11 = -5.0

# y軸の値を計算
b12 = a1[1]/a1[0] * b11

# 線形従属になるベクトルを作成
b = np.array([b11, b12])
print(b)
[-5.         -3.33333333]

 x軸の値 b_1を指定して、 \mathbf{a}_1と平行になるy軸の値 b_2 = \frac{a_{1,1}}{a_{1,2}} b_1を計算します。

  \mathbf{a}_1 \mathbf{b}が一致する係数を計算します。

# 係数を計算
beta1 = np.dot(a1, b) / np.linalg.norm(a1)**2
print(beta1)
print(beta1 * a1)
-1.6666666666666667
[-5.         -3.33333333]

  \mathbf{a}_1, \mathbf{b}は平行なので、 \mathbf{b} = \beta_1 \mathbf{a}_1で表わせました。このとき、なす角が \theta = 0の正射影ベクトルを \mathbf{p}とすると、 \mathbf{p} = \mathbf{b} = \frac{\mathbf{a}^{\top} \mathbf{b}}{\|\mathbf{a}\|^2} \mathbf{a}が成り立ちます。よって、 \mathbf{a}_1, \mathbf{b}が等しくなる係数は \beta_1 = \frac{\mathbf{b}^{\top} \mathbf{a}_1}{\|\mathbf{a}_1\|^2}で計算できます。
 正射影ベクトルについては「正射影ベクトルの計算(2・3日後に投稿予定)」を参照してください。

  \mathbf{b} = \beta_1 \mathbf{a}_1となるのを確認します。

 ベクトル \mathbf{a}_1, \mathbf{b}のグラフを作成します。

# サイズ用の値を設定
x_min = np.floor(np.min([0.0, a1[0], b[0], *x1_vals])) - 1
x_max =  np.ceil(np.max([0.0, a1[0], b[0], *x1_vals])) + 1
y_min = np.floor(np.min([0.0, a1[1], b[1], *x2_vals])) - 1
y_max =  np.ceil(np.max([0.0, a1[1], b[1], *x2_vals])) + 1

# 線形従属の2Dベクトルを作図
fig, ax = plt.subplots(figsize=(8, 6), facecolor='white')
ax.quiver(0, 0, *a1, 
          color='red', width=0.01, headwidth=5, headlength=5, 
          angles='xy', scale_units='xy', scale=1, 
          label='$a_1=('+', '.join(map(str, a1))+')$') # ベクトルa1
ax.quiver(0, 0, *b, 
          color='hotpink', width=0.01, headwidth=5, headlength=5, 
          angles='xy', scale_units='xy', scale=1, 
          label='$b=('+', '.join(map(str, b.round(2)))+')$') # ベクトルb
ax.plot(x1_vals, x2_vals, 
        label='$\\beta_1 a_1$', zorder=0) # ベクトルa1と平行な直線
ax.set_xticks(ticks=np.arange(x_min, x_max+1))
ax.set_yticks(ticks=np.arange(y_min, y_max+1))
ax.set_xlabel('$x_1$')
ax.set_ylabel('$x_2$')
ax.grid()
ax.set_title('$b=' + str(beta1.round(2)) + 'a_1$', loc='left')
fig.suptitle('linear dependent', fontsize=20)
ax.legend()
ax.set_aspect('equal')
plt.show()

2次元空間における線形従属であるベクトル集合

 線形従属な2次元ベクトルは、同一直線上の(定数倍で表わせる)ベクトルなのが分かります。
 また、2つ以上のベクトルが同一直線上のとき線形従属であると言えます。

 係数(ベクトル)の値を変化させたアニメーションを作成します。

・作図コード(クリックで展開)

# フレーム数を設定
frame_num = 51

# ベクトルを指定
a1 = np.array([3.0, 2.0])

# 係数として利用する値を指定
beta1_n = np.linspace(start=-2.0, stop=2.0, num=frame_num)

# ベクトルa1と平行な直線の座標を計算
x1_vals = beta1_n * a1[0]
x2_vals = beta1_n * a1[1]

# グラフサイズ用の値を設定
x_min = np.floor(x1_vals.min())
x_max =  np.ceil(x1_vals.max())
y_min = np.floor(x2_vals.min())
y_max =  np.ceil(x2_vals.max())

# グラフオブジェクトを初期化
fig, ax = plt.subplots(figsize=(8, 6), facecolor='white')
fig.suptitle('linear dependent', fontsize=20)

# 作図処理を関数として定義
def update(i):
    
    # 前フレームのグラフを初期化
    plt.cla()
    
    # i番目の係数を取得
    beta1 = beta1_n[i]
    
    # 線形従属になるベクトルを作成
    b = beta1 * a1
    
    # 線形従属の2Dベクトルを作図
    ax.quiver(0, 0, *a1, 
              fc='none', ec='red', ls=':', lw=2, width=0.001, headwidth=40, headlength=40, 
              angles='xy', scale_units='xy', scale=1, 
              label='$a_1=('+', '.join(map(str, a1))+')$', zorder=10) # ベクトルa1
    ax.quiver(0, 0, *b, 
              color='hotpink', width=0.01, headwidth=5, headlength=5, 
              angles='xy', scale_units='xy', scale=1, 
              label='$b=('+', '.join(map(str, b.round(2)))+')$') # ベクトルb
    ax.plot(x1_vals, x2_vals, 
            label='$\\beta_1 a_1$', zorder=0) # ベクトルa1と平行な直線
    ax.set_xticks(ticks=np.arange(x_min, x_max+1))
    ax.set_yticks(ticks=np.arange(y_min, y_max+1))
    ax.set_xlim(left=x_min, right=x_max)
    ax.set_ylim(bottom=y_min, top=y_max)
    ax.set_xlabel('$x_1$')
    ax.set_ylabel('$x_2$')
    ax.grid()
    ax.set_title('$b =' + str(beta1.round(2)) + 'a_1$', loc='left')
    ax.legend()
    ax.set_aspect('equal')
    
# gif画像を作成
ani = FuncAnimation(fig=fig, func=update, frames=frame_num, interval=100)

# gif画像を保存
ani.save('dependence_2d.gif')

 作図処理をupdate()として定義して、FuncAnimation()でgif画像を作成します。

2次元空間における線形従属であるベクトル集合

 複数のベクトルが同一直線上のベクトルであれば、それらのベクトルは定数倍で等しくなります。この例だと、 \mathbf{b}を2つ目のベクトル \mathbf{a}_2 = \mathbf{b}とすると、 \beta_1 \mathbf{a}_1 = \mathbf{a}_2です。よって、 \beta_2 = -1として( \mathbf{a}_2を移項して) \beta_1 \mathbf{a}_1 + \beta_2 \mathbf{a}_2 = \mathbf{0}となります。ここで、 \mathbf{0}は2次元の0ベクトルです。
 また、ベクトル集合に直線上でないベクトル \mathbf{a}_iが含まれていた場合は、 \beta_i = 0として \beta_1 \mathbf{a}_1 + \beta_2 \mathbf{a}_2 + \beta_i \mathbf{a}_i = \mathbf{0}が成り立ちます。このとき、 \beta_1 \neq 0, \beta_2 \neq 0なので、全ての係数が0ではないという条件を満たします。

 以上が、2次元におけるベクトルの線形従属の可視化です。

3次元の場合

 次は、3次元空間上でベクトルの線形従属を可視化します。

 3次元ベクトルを指定します。

# ベクトルを指定
a1 = np.array([-2.0, 1.5, 1.0])
a2 = np.array([-2.0, -2.0, -2.0])

 2つのベクトル \mathbf{a}_1 = (a_{1,1}, a_{1,2}, a_{1,3})^{\top}, \mathbf{a}_2 = (a_{2,1}, a_{2,2}, a_{2,3})^{\top}a1, a2として値を指定します。

 ベクトル \mathbf{a}_1, \mathbf{a}_2と平行な平面(原点と点 \mathbf{a}_1, \mathbf{a}_2を通る平面)の座標を計算します。

# 平面用の係数を作成
beta_vals = np.arange(start=-1.5, stop=1.6, step=0.5)
print(beta_vals)
print(beta_vals.shape)

# 格子点を作成
beta1_grid, beta2_grid = np.meshgrid(beta_vals, beta_vals)
print(beta1_grid.shape)

# ベクトルa1,a2と平行な平面の座標を計算
x1_grid = beta1_grid * a1[0] + beta2_grid * a2[0]
x2_grid = beta1_grid * a1[1] + beta2_grid * a2[1]
x3_grid = beta1_grid * a1[2] + beta2_grid * a2[2]
print(x1_grid[:5, :5])
print(x2_grid[:5, :5])
print(x3_grid[:5, :5])
[-1.5 -1.  -0.5  0.   0.5  1.   1.5]
(7,)
(7, 7)
[[ 6.  5.  4.  3.  2.]
 [ 5.  4.  3.  2.  1.]
 [ 4.  3.  2.  1.  0.]
 [ 3.  2.  1. -0. -1.]
 [ 2.  1.  0. -1. -2.]]
[[ 0.75  1.5   2.25  3.    3.75]
 [-0.25  0.5   1.25  2.    2.75]
 [-1.25 -0.5   0.25  1.    1.75]
 [-2.25 -1.5  -0.75  0.    0.75]
 [-3.25 -2.5  -1.75 -1.   -0.25]]
[[ 1.5  2.   2.5  3.   3.5]
 [ 0.5  1.   1.5  2.   2.5]
 [-0.5  0.   0.5  1.   1.5]
 [-1.5 -1.  -0.5  0.   0.5]
 [-2.5 -2.  -1.5 -1.  -0.5]]

  \mathbf{a}_1, \mathbf{a}_2を線形結合したベクトル \mathbf{b} = \beta_1 \mathbf{a}_1 + \beta_2 \mathbf{a}_2がとり得る値(座標)を計算します。
  \beta_1, \beta_2の値をbeta_valsとし、np.meshgrid()で格子状の点(全ての組み合わせ)を作成してbeta*_gridとして、x軸・y軸・z軸の値 b_i = \beta_1 a_{1,i} + \beta_2 a_{2,i}を計算してx*_gridとします。

 3次元空間上に、ベクトル \mathbf{a}_1, \mathbf{a}_2と平面のグラフを作成します。

# グラフサイズ用の値を設定
x_min = np.floor(x1_grid.min())
x_max =  np.ceil(x1_grid.max())
y_min = np.floor(x2_grid.min())
y_max =  np.ceil(x2_grid.max())
z_min = np.floor(x3_grid.min())
z_max =  np.ceil(x3_grid.max())

# 矢のサイズを指定
l = 0.6

# 線形独立の3Dベクトルを作図
fig, ax = plt.subplots(figsize=(8, 8), facecolor='white', 
                       subplot_kw={'projection': '3d'})
ax.quiver(0, 0, 0, *a1, 
          color='red', linewidth=3, arrow_length_ratio=l/np.linalg.norm(a1), 
          label='$a_1=('+', '.join(map(str, a1))+')$') # ベクトルa1
ax.quiver(0, 0, 0, *a2, 
          color='blue', linewidth=3, arrow_length_ratio=l/np.linalg.norm(a2), 
          label='$a_2=('+', '.join(map(str, a2))+')$') # ベクトルa2
ax.plot_wireframe(x1_grid, x2_grid, x3_grid, 
                  label='$\\beta_1 a_1 + \\beta_2 a_2$') # ベクトルa1,a2と平行な平面
ax.quiver([0, a1[0], a2[0]], [0, a1[1], a2[1]], [0, a1[2], a2[2]], 
          [0, 0, 0], [0, 0, 0], [z_min, z_min-a1[2], z_min-a2[2]], 
          color=['gray', 'red', 'blue'], arrow_length_ratio=0, linestyle=':') # 座標用の補助線
#ax.set_xticks(ticks=np.arange(x_min, x_max+1))
#ax.set_yticks(ticks=np.arange(y_min, y_max+1))
#ax.set_zticks(ticks=np.arange(z_min, z_max+1))
ax.set_zlim(bottom=z_min, top=z_max)
ax.set_xlabel('$x_1$')
ax.set_ylabel('$x_2$')
ax.set_zlabel('$x_3$')
fig.suptitle('linear independent', fontsize=20)
ax.legend()
ax.set_aspect('equal')
#ax.view_init(elev=90, azim=270) # xy面
#ax.view_init(elev=0, azim=270) # xz面
#ax.view_init(elev=0, azim=0) # yz面
plt.show()

3次元空間における線形独立であるベクトル集合

 axes.quiver()で3次元ベクトルを描画します。第1・2・3引数に始点の座標、第4・5・6引数にベクトルのサイズ(移動量)を指定します。
 配列a1, a2の前に*を付けてアンパック(展開)して座標を指定します。始点は原点です。

 arrow_length_ratio引数で矢のサイズ(全体に対する矢の割合)を指定できます。この例では、各ベクトルのノルムの逆数を指定することで、ベクトルのサイズに関わらず、全ての矢のサイズを統一します。
 さらに、全てのベクトルで同じ値lを掛けることで、サイズを調整できます。

 ベクトル \mathbf{a}_1, \mathbf{a}_2それぞれと平行な直線によって平面が作られるのが分かります。
 また、原点と点 \mathbf{a}_1, \mathbf{a}_2を通る平面が、ベクトル \mathbf{a}_1, \mathbf{a}_2と平行なのを確認できます。

  \mathbf{a}_1, \mathbf{a}_2, \mathbf{b}が線形従属になるベクトルを作成します。

# x軸とy軸の値を指定
b11 = 4.0
b12 = 2.0

# z軸の値を計算
p = np.cross(a1, a2) # 外積
b13 = - p[0]/p[2] * b11 - p[1]/p[2] * b12

# 線形従属になるベクトルを作成
b = np.array([b11, b12, b13])
print(b)
[4.         2.         2.28571429]

 x軸・y軸の値 b_1, b_2を指定して、 \mathbf{a}_1, \mathbf{a}_2と平行になるz軸の値 b_3 = \frac{p_1}{p_3} b_1 + \frac{p_2}{p_3} b_2を計算します。ここで、 \mathbf{p} = (p_1, p_2, p_3)^{\top}は、 \mathbf{a}_1, \mathbf{a}_2の外積 \mathbf{p} = \mathbf{a}_1 \times \mathbf{a}_2です。
 詳しくは「原点と2点を通る平面上の点を求めたい」を参照してください。

  \mathbf{a}_1, \mathbf{a}_2の線形結合と \mathbf{b}が一致する係数を計算したかったのですが、「後の章で、ベクトルの基底展開の係数を求める方法を説明する」がどの章なのか分からないし何も分からないので分かるまで飛ばします、、、

 ベクトル \mathbf{a}_1, \mathbf{a}_2, \mathbf{b}のグラフを作成します。

# グラフサイズ用の値を設定
x_min = np.floor(np.min([0.0, a1[0], a2[0], b[0], *x1_grid.flatten()]))
x_max =  np.ceil(np.max([0.0, a1[0], a2[0], b[0], *x1_grid.flatten()]))
y_min = np.floor(np.min([0.0, a1[1], a2[1], b[1], *x2_grid.flatten()]))
y_max =  np.ceil(np.max([0.0, a1[1], a2[1], b[1], *x2_grid.flatten()]))
z_min = np.floor(np.min([0.0, a1[2], a2[2], b[2], *x3_grid.flatten()]))
z_max =  np.ceil(np.max([0.0, a1[2], a2[2], b[2], *x3_grid.flatten()]))

# 矢のサイズを指定
l = 0.6

# ラベル用の符号を設定
#sgn2 = '+' if beta2 >= 0.0 else ''

# 線形従属の3Dベクトルを作図
fig, ax = plt.subplots(figsize=(8, 8), facecolor='white', 
                       subplot_kw={'projection': '3d'})
ax.quiver(0, 0, 0, *a1, 
          color='red', linewidth=3, arrow_length_ratio=l/np.linalg.norm(a1), 
          label='$a_1=('+', '.join(map(str, a1))+')$') # ベクトルa1
ax.quiver(0, 0, 0, *a2, 
          color='blue', linewidth=3, arrow_length_ratio=l/np.linalg.norm(a2), 
          label='$a_2=('+', '.join(map(str, a2))+')$') # ベクトルa2
ax.quiver(0, 0, 0, *b, 
          color='purple', linewidth=3, arrow_length_ratio=l/np.linalg.norm(b), 
          label='$b=('+', '.join(map(str, b.round(2)))+')$') # ベクトルb
ax.plot_wireframe(x1_grid, x2_grid, x3_grid, 
                  label='$\\beta_1 a_1 + \\beta_2 a_2$') # ベクトルa1,a2と平行な平面
ax.quiver([0, a1[0], a2[0], b[0]], [0, a1[1], a2[1], b[1]], [0, a1[2], a2[2], b[2]], 
          [0, 0, 0, 0], [0, 0, 0, 0], [z_min, z_min-a1[2], z_min-a2[2], z_min-b[2]], 
          color=['gray', 'red', 'blue', 'purple'], arrow_length_ratio=0, linestyle=':') # 座標用の補助線
#ax.set_xticks(ticks=np.arange(x_min, x_max+1))
#ax.set_yticks(ticks=np.arange(y_min, y_max+1))
#ax.set_zticks(ticks=np.arange(z_min, z_max+1))
ax.set_zlim(bottom=z_min, top=z_max)
ax.set_xlabel('$x_1$')
ax.set_ylabel('$x_2$')
ax.set_zlabel('$x_3$')
#ax.set_title('$b=' + str(beta1.round(2))+'a_1' + sgn2+str(beta2.round(2))+'a_2$', loc='left')
fig.suptitle('linear dependent', fontsize=20)
ax.legend()
ax.set_aspect('equal')
#ax.view_init(elev=90, azim=270) # xy面
#ax.view_init(elev=0, azim=270) # xz面
#ax.view_init(elev=0, azim=0) # yz面
plt.show()

3次元空間における線形従属であるベクトル集合

3次元空間における線形従属であるベクトル集合


 グラフを回転させて確認します。

・作図コード(クリックで展開)

# フレーム数を指定
frame_num = 60

# 水平方向の角度として利用する値を作成
h_n = np.linspace(start=0.0, stop=360.0, num=frame_num+1)[:frame_num]

# ラベル用の符号を設定
#sgn2 = '+' if beta2 >= 0.0 else ''

# グラフオブジェクトを初期化
fig, ax = plt.subplots(figsize=(8, 8), facecolor='white', 
                       subplot_kw={'projection': '3d'})
fig.suptitle('linear dependent', fontsize=20)

# 作図処理を関数として定義
def update(i):
    
    # 前フレームのグラフを初期化
    plt.cla()

    # i番目の角度を取得
    h = h_n[i]
    
    # 線形従属の3Dベクトルを作図
    ax.quiver(0, 0, 0, *a1, 
              color='red', linewidth=3, arrow_length_ratio=l/np.linalg.norm(a1), 
              label='$a_1=('+', '.join(map(str, a1))+')$') # ベクトルa1
    ax.quiver(0, 0, 0, *a2, 
              color='blue', linewidth=3, arrow_length_ratio=l/np.linalg.norm(a2), 
              label='$a_2=('+', '.join(map(str, a2))+')$') # ベクトルa2
    ax.quiver(0, 0, 0, *b, 
              color='purple', linewidth=3, arrow_length_ratio=l/np.linalg.norm(b), 
              label='$b=('+', '.join(map(str, b.round(2)))+')$') # ベクトルb
    ax.plot_wireframe(x1_grid, x2_grid, x3_grid, 
                      label='$\\beta_1 a_1 + \\beta_2 a_2$') # ベクトルa1,a2と平行な平面
    ax.quiver([0, a1[0], a2[0], b[0]], [0, a1[1], a2[1], b[1]], [0, a1[2], a2[2], b[2]], 
              [0, 0, 0, 0], [0, 0, 0, 0], [z_min, z_min-a1[2], z_min-a2[2], z_min-b[2]], 
              color=['gray', 'red', 'blue', 'purple'], arrow_length_ratio=0, linestyle=':') # 座標用の補助線
    ax.set_zlim(bottom=z_min, top=z_max)
    ax.set_xlabel('$x_1$')
    ax.set_ylabel('$x_2$')
    ax.set_zlabel('$x_3$')
    #ax.set_title('$b=' + str(beta1.round(2))+'a_1' + sgn2+str(beta2.round(2))+'a_2$', loc='left')
    ax.legend()
    ax.set_aspect('equal')
    ax.view_init(elev=30, azim=h) # 表示角度

# gif画像を作成
ani = FuncAnimation(fig=fig, func=update, frames=frame_num, interval=100)

# gif画像を保存
ani.save('dependence360_3d.gif')

3次元空間における線形従属であるベクトル集合

 線形従属な3次元ベクトルは、同一平面上の(線形結合で表わせる)ベクトルなのが分かります。
 また、3つ以上のベクトルが同一平面上のとき線形従属であると言えます。

 係数(ベクトル)の値を変化させたアニメーションを作成します。

・作図コード(クリックで展開)

# フレーム数を設定
frame_num = 51

# ベクトルを指定
a1 = np.array([-2.0, 1.5, 1.0])
a2 = np.array([-2.0, -2.0, -2.0])

# 平面用の係数を作成
beta_vals = np.arange(start=-1.5, stop=1.6, step=0.5)
beta1_grid, beta2_grid = np.meshgrid(beta_vals, beta_vals)

# ベクトルa1,a2と平行な平面の座標を計算
x1_grid = beta1_grid * a1[0] + beta2_grid * a2[0]
x2_grid = beta1_grid * a1[1] + beta2_grid * a2[1]
x3_grid = beta1_grid * a1[2] + beta2_grid * a2[2]

# グラフサイズ用の値を設定
x_min = np.floor(np.min([0.0, a1[0], a2[0], *x1_grid.flatten()]))
x_max =  np.ceil(np.max([0.0, a1[0], a2[0], *x1_grid.flatten()]))
y_min = np.floor(np.min([0.0, a1[1], a2[1], *x2_grid.flatten()]))
y_max =  np.ceil(np.max([0.0, a1[1], a2[1], *x2_grid.flatten()]))
z_min = np.floor(np.min([0.0, a1[2], a2[2], *x3_grid.flatten()]))
z_max =  np.ceil(np.max([0.0, a1[2], a2[2], *x3_grid.flatten()]))

# 係数の作成用の値(ラジアン)として利用する値を指定
t_n = np.linspace(start=0.0, stop=2.0*np.pi, num=frame_num+1)[:frame_num]

# ベクトルのサイズ調整用の値を指定
r = 1.0

# 矢印のサイズを指定
l = 0.6

# グラフオブジェクトを初期化
fig, ax = plt.subplots(figsize=(8, 8), facecolor='white', 
                       subplot_kw={'projection': '3d'})
fig.suptitle('linear dependent', fontsize=20)

# 作図処理を関数として定義
def update(i):
    
    # 前フレームのグラフを初期化
    plt.cla()
    
    # i番目の係数を作成
    t = t_n[i]
    beta1 = r * np.cos(t)
    beta2 = r * np.sin(t)
    
    # 線形従属になるベクトルを作成
    b = beta1 * a1 + beta2 * a2
    
    # ラベル用の符号を設定
    sign2 = '+' if beta2 >= 0.0 else ''
    
    # 線形従属の3Dベクトルを作図
    ax.quiver(0, 0, 0, *a1, 
              color='red', linewidth=3, arrow_length_ratio=l/np.linalg.norm(a1), 
              label='$a_1=('+', '.join(map(str, a1))+')$') # ベクトルa1
    ax.quiver(0, 0, 0, *a2, 
              color='blue', linewidth=3, arrow_length_ratio=l/np.linalg.norm(a2), 
              label='$a_2=('+', '.join(map(str, a2))+')$') # ベクトルa2
    ax.quiver(0, 0, 0, *b, 
              color='purple', linewidth=3, arrow_length_ratio=l/np.linalg.norm(b), 
              label='$b=('+', '.join(map(str, b.round(2)))+')$') # ベクトルb
    ax.plot_wireframe(x1_grid, x2_grid, x3_grid, 
                      label='$\\beta_1 a_1 + \\beta_2 a_2$', zorder=-100) # ベクトルa1,a2と平行な平面
    ax.quiver([0, a1[0], a2[0], b[0]], [0, a1[1], a2[1], b[1]], [0, a1[2], a2[2], b[2]], 
              [0, 0, 0, 0], [0, 0, 0, 0], [z_min, z_min-a1[2], z_min-a2[2], z_min-b[2]], 
              color=['gray', 'red', 'blue', 'purple'], arrow_length_ratio=0, linestyle=':') # 補助線
    ax.set_zlim(bottom=z_min, top=z_max)
    ax.set_xlabel('$x_1$')
    ax.set_ylabel('$x_2$')
    ax.set_zlabel('$x_3$')
    ax.set_title('$b=' + str(beta1.round(2))+'a_1' + sign2+str(beta2.round(2))+'a_2$', loc='left')
    ax.legend(loc='upper left')
    ax.set_aspect('equal')
    #ax.view_init(elev=90, azim=270) # xy面
    #ax.view_init(elev=0, azim=270) # xz面
    #ax.view_init(elev=0, azim=0) # yz面

# gif画像を作成
ani = FuncAnimation(fig=fig, func=update, frames=frame_num, interval=100)

# gif画像を保存
ani.save('dependence_3d.gif')

3次元空間における線形従属であるベクトル集合

 サイズrを変更すると、ベクトル \mathbf{b}が平面(とその延長)上の全ての点になり得るのを確認できます。

 3つ以上のベクトルが同一平面上のベクトルであれば、それらのベクトルは線形結合で等しくなります。この例だと、 \mathbf{b}を3つ目のベクトル \mathbf{a}_3 = \mathbf{b}とすると、 \beta_1 \mathbf{a}_1 + \beta_2 \mathbf{a}_2 = \mathbf{a}_3です。よって、 \beta_3 = -1として( \mathbf{a}_3を移項して) \beta_1 \mathbf{a}_1 + \beta_2 \mathbf{a}_2 + \beta_3 \mathbf{a}_3 = \mathbf{0}となります。ここで、 \mathbf{0}は3次元の0ベクトルです。
 また、ベクトル集合に平面上でないベクトル \mathbf{a}_iが含まれていた場合は、 \beta_i = 0として \beta_1 \mathbf{a}_1 + \beta_2 \mathbf{a}_2 + \beta_3 \mathbf{a}_3 + \beta_i \mathbf{a}_i = \mathbf{0}が成り立ちます。このとき、 \beta_1 \neq 0, \beta_2 \neq 0, \beta_3 \neq 0なので、全ての係数が0ではないという条件を満たします。

 以上が、3次元におけるベクトルの線形従属の可視化です。

 この記事では、線形従属なベクトルを可視化しました。次の記事では、線形独立なベクトルを可視化します。

参考書籍

  • Stephen Boyd・Lieven Vandenberghe(著),玉木 徹(訳)『スタンフォード ベクトル・行列からはじめる最適化数学』講談社サイエンティク,2021年.

おわりに

 初見では線形従属と線形独立の説明が何を言っているのか全然分かりませんでした。が、1日1記事更新を諦めてじっくり読んでたらなんとか理解できたと思います。どうやら、線形従属でなければ線形独立であるまたその逆も成り立つみたいな説明の仕方に慣れてなかったようです。
 そんな感じで対の内容なので次の記事もセットで読んでください。

 つまり、みんなのベクトルが揃ってたらダメってことですね。

 最後に、先日公開されたエビ中の新曲をどうぞ♪

 今日どうする?共同する?

【次の内容】

www.anarchive-beta.com