跳轉到

1.4 視覺化工具箱:建立 linalg_viz.py#

本節目標#

  • 建立貫穿全書的視覺化模組 linalg_viz.py
  • 寫出三個核心函數:plot_vectors()、plot_transformation()、plot_unit_circle_transformed()
  • 學會在之後的章節 import 使用

為什麼需要工具箱#

之後每一章都會反覆做同樣的事:畫向量、畫變換前後的圖形。 與其在每章複製貼上程式碼,不如寫一個小模組。這也是真實軟體的工作方式。


完整程式碼:linalg_viz.py#

請在與各章 notebook 同一個資料夾建立 linalg_viz.py,內容如下:

# linalg_viz.py —— 本書共用的視覺化工具箱
import numpy as np
import matplotlib.pyplot as plt


def plot_vectors(vectors, colors=None, labels=None, xlim=(-5, 5),
                 ylim=(-5, 5), title="", ax=None, show=True):
    """從原點畫出多個向量。

    參數
    ----
    vectors : list of array-like,每個元素是 [x, y](或 [x, y, z])
    colors  : list,顏色字串或名稱;None 時自動配色
    labels  : list of str,圖例文字
    xlim, ylim : 座標範圍
    title   : 標題
    ax      : 傳入 matplotlib axes 則畫在上面(不另開圖)
    show    : 是否立即 plt.show()
    """
    vectors = [np.asarray(v, dtype=float) for v in vectors]
    n = len(vectors)
    if colors is None:
        colors = [f"C{i}" for i in range(n)]
    if labels is None:
        labels = [f"v{i}" for i in range(n)]

    own_fig = ax is None
    if own_fig:
        fig, ax = plt.subplots(figsize=(5, 5))
    for v, c, lab in zip(vectors, colors, labels):
        ax.quiver(0, 0, v[0], v[1], angles='xy', scale_units='xy',
                  scale=1, color=c, label=lab, width=0.006)
    ax.set_xlim(*xlim); ax.set_ylim(*ylim)
    ax.grid(alpha=0.4)
    ax.axhline(0, color='gray', lw=0.8)
    ax.axvline(0, color='gray', lw=0.8)
    ax.set_aspect('equal')
    ax.legend()
    if title:
        ax.set_title(title)
    if own_fig and show:
        plt.show()
    return ax


def plot_transformation(A, xlim=(-3, 3), ylim=(-3, 3), title="矩陣變換"):
    """畫出單位方形與網格經矩陣 A 變換前後的樣子。

    參數
    ----
    A : 2x2 矩陣(ndarray 或 list)
    """
    A = np.asarray(A, dtype=float)

    # 單位方形的四個角
    square = np.array([[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]])
    transformed = square @ A.T          # 每一點都乘上 A

    # 背景網格線
    fig, axes = plt.subplots(1, 2, figsize=(11, 5))
    for grid_n in range(-3, 4):
        line_h = np.array([[x, grid_n] for x in np.linspace(-3, 3, 2)])
        line_v = np.array([[grid_n, y] for y in np.linspace(-3, 3, 2)])
        for ax, pts in [(axes[0], line_h), (axes[0], line_v)]:
            ax.plot(pts[:, 0], pts[:, 1], color='lightgray', lw=0.7)
        for ax, pts in [(axes[1], line_h @ A.T), (axes[1], line_v @ A.T)]:
            ax.plot(pts[:, 0], pts[:, 1], color='lightblue', lw=0.7)

    axes[0].plot(square[:, 0], square[:, 1], 'b-o', label='變換前')
    axes[1].plot(transformed[:, 0], transformed[:, 1], 'r-o', label='變換後')

    for ax in axes:
        ax.set_xlim(*xlim); ax.set_ylim(*ylim)
        ax.set_aspect('equal'); ax.grid(alpha=0.3); ax.legend()
    axes[0].set_title('變換前')
    axes[1].set_title(f'變換後  A = {A.tolist()}')
    fig.suptitle(title)
    plt.show()


def plot_unit_circle_transformed(A, n_points=100):
    """畫單位圓經矩陣 A 變換後的形狀(通常是橢圓)。"""
    A = np.asarray(A, dtype=float)
    theta = np.linspace(0, 2 * np.pi, n_points)
    circle = np.column_stack([np.cos(theta), np.sin(theta)])
    transformed = circle @ A.T          # (n, 2)

    fig, ax = plt.subplots(figsize=(6, 6))
    ax.plot(circle[:, 0], circle[:, 1], 'b--', label='單位圓(變換前)')
    ax.plot(transformed[:, 0], transformed[:, 1], 'r-', label='變換後')
    ax.set_aspect('equal'); ax.grid(alpha=0.4); ax.legend()
    ax.set_title(f'單位圓經 A = {A.tolist()} 變換')
    plt.show()

擴充函式(第 2 章起會用到)#

第 2 章開始需要「變換前後網格疊在同一張圖」的畫法與自訂座標軸, 請把下列三個函式 加到 linalg_viz.py 尾端(之後章節一樣 import):

# ---- 以下加入 linalg_viz.py ----

def setup_axes(ax, xlim=(-5, 5), ylim=(-5, 5)):
    """準備一個等比例、帶格線與座標軸的畫面。"""
    ax.set_xlim(*xlim); ax.set_ylim(*ylim)
    ax.set_aspect('equal')
    ax.grid(alpha=0.3)
    ax.axhline(0, color='gray', lw=0.8)
    ax.axvline(0, color='gray', lw=0.8)


def draw_grid_transform(ax, A, xlim=(-5, 5), ylim=(-5, 5), n=6,
                        show_before=True):
    """在同一張圖上畫出單位方格網經 A 變換前(淡)後(深)的樣子。

    A : 2x2 矩陣;n : 網格範圍 ±n
    """
    A = np.asarray(A, dtype=float)
    xs = np.linspace(-n, n, 2)
    if show_before:                      # 變換前的原始網格
        for g in range(-n, n + 1):
            ax.plot([-n, n], [g, g], color='lightgray', lw=0.6)
            ax.plot([g, g], [-n, n], color='lightgray', lw=0.6)
    # 變換後的網格:水平線與垂直線各畫一組
    for g in range(-n, n + 1):
        h = np.column_stack([xs * 0 + g, xs])          # y = g 的水平線
        v = np.column_stack([xs, xs * 0 + g])          # x = g 的垂直線
        for pts in (h, v):
            t = pts @ A.T                              # 變換!
            ax.plot(t[:, 0], t[:, 1], color='steelblue', lw=1.0)
    # 追蹤基向量 e1, e2 的像
    for e, c in [(np.array([1., 0.]), 'tab:red'),
                 (np.array([0., 1.]), 'tab:green')]:
        w = A @ e
        ax.quiver(0, 0, w[0], w[1], angles='xy', scale_units='xy',
                  scale=1, color=c, width=0.006)
    ax.set_xlim(*xlim); ax.set_ylim(*ylim)
    ax.set_aspect('equal')
    ax.axhline(0, color='gray', lw=0.8)
    ax.axvline(0, color='gray', lw=0.8)


def draw_vectors(ax, vec_map):
    """在既有 axes 上疊畫多個向量(箭頭)。

    vec_map : {標籤: 向量} 的字典;標籤含 'T(' 的畫實線,其餘畫虛線
    """
    for i, (lab, v) in enumerate(vec_map.items()):
        v = np.asarray(v, dtype=float)
        style = '-' if 'T(' in lab else '--'
        ax.arrow(0, 0, v[0], v[1], head_width=0.12,
                 length_includes_head=True, color=f'C{i}',
                 linestyle=style, label=lab)
    ax.legend()
    return ax

示範(2.1 節會重複用到):

import numpy as np
import matplotlib.pyplot as plt
from linalg_viz import setup_axes, draw_grid_transform, draw_vectors

A = np.array([[1., 1.], [0., 1.]])         # 剪切
fig, ax = plt.subplots(figsize=(6, 6))
setup_axes(ax, xlim=(-1, 4), ylim=(-1, 4))
draw_grid_transform(ax, A, xlim=(-1, 4), ylim=(-1, 4))
plt.show()

輸出描述:淡灰色原始方格網 + 深藍色被剪歪的網格,紅、綠箭頭是 \(\mathbf{e_1}, \mathbf{e_2}\) 變換後的行向量像。

函式選擇速查:

函式 用途 使用章節
plot_vectors 畫多支箭頭 全書
plot_transformation 單位方形變換前後(兩張圖) 2.x
plot_unit_circle_transformed 單位圓 → 橢圓 5、7
setup_axes 自訂等比例軸 2 起
draw_grid_transform 網格變換前後疊圖 2 全章
draw_vectors 疊畫向量(含虛線原向量) 2.x

參數說明與示範#

plot_vectors()#

  • vectors:向量列表,內部先轉成 float ndarray
  • colors / labels:自動配 C0, C1, ... 與 v0, v1, ...
  • ax:傳入既有 axes 可疊圖(第 3 章會用到)

示範:

from linalg_viz import plot_vectors

plot_vectors([[2, 1], [1, 3], [3, 4]],
             labels=['u', 'v', 'u+v'],
             title='向量相加')

輸出描述:座標平面上三支箭頭,紅色 u+v 是藍、橘兩箭頭張出的平行四邊形對角線, 圖例與等比例座標網格一應俱全。

plot_transformation()#

示範:用一個「剪變」矩陣:

from linalg_viz import plot_transformation

A = np.array([[1, 1],
              [0, 1]])          # 往右推的剪變
plot_transformation(A, title='剪變換')

輸出描述:左圖是淡灰方格網與藍色單位方形;右圖整個網格被往右推歪、 越上面推越多,單位方形變成斜的平行四邊形——這就是矩陣 \(A\) 對空間做的事。

plot_unit_circle_transformed()#

示範:

from linalg_viz import plot_unit_circle_transformed

A = np.array([[2, 0],
              [0, 1]])          # x 方向拉長 2 倍
plot_unit_circle_transformed(A)

輸出描述:藍色虛線單位圓變成紅色橢圓,長軸在 x 方向、半徑 2。 第 8 章你會知道:橢圓的長短軸方向就是 \(A\) 的特徵向量方向。


之後的章節如何使用#

只要 linalg_viz.py 與 notebook 在同一資料夾(或已加入 sys.path):

from linalg_viz import plot_vectors, plot_transformation, plot_unit_circle_transformed

之後章節的使用場景:

章節 用哪個函數
第 2 章 矩陣乘法 plot_transformation() 拆解複合變換
第 3 章 線性變換 plot_vectors() 觀察基向量去哪了
第 5 章 行列式 plot_transformation() 看面積縮放
第 8 章 特徵值 plot_unit_circle_transformed() 找不變方向

存檔與執行環境提示#

  1. 檔案位置:linalg_viz.py 放在 notebook 同目錄;否則用 sys.path.append('/你的資料夾路徑')。
  2. 執行環境:建議 Jupyter Notebook / JupyterLab(圖會內嵌顯示)。 若用純 Python 腳本執行,圖會開在獨立視窗,記得 plt.show()。
  3. 安裝:pip install numpy matplotlib(或用 conda install)。
  4. 驗證安裝:python -c "import numpy, matplotlib; print('OK')"。
  5. 若在 Colab 執行,把 linalg_viz.py 用左側檔案面板上傳,或 from google.colab import files; files.upload()。

小結與練習#

本節重點 1. linalg_viz.py 是全書的視覺化基礎設施,三個函數各司其職。 2. plot_transformation 左右對照圖是理解「矩陣 = 變換」的最強工具。 3. 修改網格線的 @ A.T 是 broadcasting + 矩陣乘法的綜合應用。

練習 1. 用 plot_transformation 測試旋轉矩陣 \(\begin{bmatrix} 0 & -1 \\ 1 & 0 \end{bmatrix}\) ,方形轉了幾度? 2. 測試 \(\begin{bmatrix} 0 & 0 \\ 0 & 1 \end{bmatrix}\) ,觀察網格發生什麼?(這是「投影」,空間被壓扁,行列式 = 0 的預告) 3. 幫 plot_vectors 加上 alpha 參數控制透明度。 4. 寫一個 plot_points_combination(u, v, alphas, betas) 函數,畫出 1.3 節的線性組合網格。