模型預測控制入門:從有限時域最佳化到即時求解

2026-07-23
  • mpc
  • optimal-control
  • quadratic-programming
  • state-space
  • receding-horizon

問題定義

PID 控制器(見PID 控制器完整拆解)只根據「現在」的誤差反應,不知道系統未來會怎麼演變,也沒有機制原生處理致動器極限或狀態限制。模型預測控制(Model Predictive Control, MPC)從根本上換了一個思路:在每個控制週期,用一個系統動態模型,預測接下來一段有限時域內、在不同控制輸入序列下系統會怎麼演變,然後解一個最佳化問題,找出讓這段時域內某個成本函數(通常同時衡量「跟目標的偏差」與「控制輸出的大小」)最小的控制序列——但只執行這個序列的第一步,下一個控制週期用新的量測值重新預測、重新求解。這個「只執行第一步、每步都重新求解」的策略稱為遞推時域(Receding Horizon),是 MPC 名字裡「Predictive」真正的實作方式,也是它跟開迴路軌跡最佳化最關鍵的差異——每一步都用最新的量測值修正模型誤差與外部擾動,本質上仍然是一種反饋控制。

狀態空間模型

線性 MPC 建立在離散時間線性狀態空間模型上:

xk+1=Axk+Bukx_{k+1} = A x_k + B u_k

xkRnx_k \in \mathbb{R}^n 是系統狀態(例如移動機器人的位置、速度;機械手臂的關節角度、角速度),ukRmu_k \in \mathbb{R}^m 是控制輸入,AABB 是描述系統動態的矩陣。這個模型可能是系統真實動態的線性近似(例如在某個工作點附近對非線性動態做泰勒展開取一階項),也可能是本身就接近線性的系統(例如倒單擺在接近直立點附近的動態)。

有限時域最佳化問題

在第 kk 步,MPC 要解的問題是:給定目前狀態 xkx_k,找出未來 NN 步的控制序列 uk,uk+1,,uk+N1u_k, u_{k+1}, \dots, u_{k+N-1},使得以下二次型成本函數最小:

minuk,,uk+N1 i=0N1(xk+ixrefQ2+uk+iR2)+xk+NxrefQf2\min_{u_k, \dots, u_{k+N-1}} \ \sum_{i=0}^{N-1} \left( \|x_{k+i} - x_{\text{ref}}\|_Q^2 + \|u_{k+i}\|_R^2 \right) + \|x_{k+N} - x_{\text{ref}}\|_{Q_f}^2 s.t.xk+i+1=Axk+i+Buk+i,uminuk+iumax,xminxk+ixmax\text{s.t.} \quad x_{k+i+1} = A x_{k+i} + B u_{k+i}, \qquad u_{\min} \le u_{k+i} \le u_{\max}, \qquad x_{\min} \le x_{k+i} \le x_{\max}

其中 vQ2=vTQv\|v\|_Q^2 = v^T Q vQQ 是狀態誤差權重矩陣,RR 是控制輸出權重矩陣,QfQ_f 是時域終點的終端成本權重(通常設得比 QQ 大,鼓勵系統在時域結束時已經接近目標,這對確保遞推時域策略的穩定性有實質幫助)。

這個成本函數的設計直接體現了控制問題裡最基本的 trade-off:QQ 相對 RR 越大,控制器越積極地消除狀態誤差、但也會用更大的控制輸出(可能更耗能、動作更劇烈);RR 相對 QQ 越大,控制輸出越平滑保守、但跟蹤目標的速度變慢。這組權重的選擇是 MPC 實務調參的核心工作,概念上跟 PID 調整 KpK_pKiK_iKdK_d 三個增益的角色類似,但因為是矩陣形式,還能對狀態向量裡不同分量(例如「位置誤差」跟「速度誤差」)給予不同的重視程度。

約束條件是 MPC 相對 PID 最核心的結構性優勢:致動器極限 uminuumaxu_{\min} \le u \le u_{\max}、狀態限制 xminxxmaxx_{\min} \le x \le x_{\max}(例如關節角度不能超過機構極限、移動平台速度不能超過安全上限)直接寫進最佳化問題的約束條件,求解器保證輸出的控制序列滿足這些限制;PID 沒有這種機制,只能在輸出端事後做飽和限幅(見 PID 控制器完整拆解 中的抗飽和一節),這是被動補救,不是主動規劃。

凝聚形式與二次規劃求解

上述問題可以透過把未來狀態 xk+1,,xk+Nx_{k+1}, \dots, x_{k+N} 全部用初始狀態 xkx_k 與控制序列表示(反覆代入狀態方程式),消去狀態變數,只留下控制序列 u=[ukT,,uk+N1T]T\mathbf{u} = [u_k^T, \dots, u_{k+N-1}^T]^T 作為決策變數,這個過程稱為凝聚(condensing)。凝聚後,原本的問題化簡成標準二次規劃(Quadratic Programming, QP)形式:

minu 12uTHu+fTus.t.Guh\min_{\mathbf{u}} \ \frac{1}{2} \mathbf{u}^T H \mathbf{u} + f^T \mathbf{u} \qquad \text{s.t.} \quad G \mathbf{u} \le h

HHffGGhh 都可以從 AABBQQRRxkx_k 與約束邊界事先推導出解析公式(矩陣運算,不含未知數的迭代求解),每個控制週期只需要代入最新的 xkx_k 重新算出 ffHHGGhh 在線性時不變系統下通常整個控制過程都不變,可以離線算好),再呼叫標準 QP 求解器求解——這也是為什麼線性 MPC 即使要在每個控制週期重新求解一個最佳化問題,仍然能做到相對高頻率(幾十到幾百 Hz)的即時控制:QP 求解器對凝聚後的中等規模問題,求解時間通常在毫秒等級。

線性 MPC vs 非線性 MPC

線性 MPC(Linear MPC, LMPC) 假設系統動態可以用線性模型(或分段線性近似)描述,優點是能化簡成上述標準 QP 問題,QP 是凸最佳化問題,有成熟且高效的求解器(可以保證找到全域最佳解,求解時間也相對可預測),這是線性 MPC 能在中高頻率控制迴路裡即時運行的關鍵原因。缺點是對高度非線性的系統(大幅度姿態變化、接觸力學)誤差會顯著累積。

非線性 MPC(Nonlinear MPC, NMPC) 直接對非線性動態模型 xk+1=f(xk,uk)x_{k+1} = f(x_k, u_k) 求解,成本函數與約束也可能是非線性的,問題不再是凸的,沒有保證能找到全域最佳解,常見求解方法是序列凸規劃(Sequential Convex Programming, SCP,每一步在目前解附近線性化再解一個 QP,反覆迭代逼近非線性問題的解)、iLQR(iterative Linear Quadratic Regulator)或 DDP(Differential Dynamic Programming)。四足與雙足機器人的動態步態規劃是 NMPC 近幾年最活躍的應用場景之一——足式機器人的接觸力學(腳何時觸地、摩擦力錐約束)本質上是高度非線性甚至非光滑的,線性近似會產生顯著誤差,直到近幾年隨著嵌入式運算能力提升與更高效的 NMPC 求解器(例如 MIT Cheetah 系列與其後續工作採用的凸 MPC 簡化版本)出現,NMPC/簡化非線性 MPC 才逐漸能在四足機器人上做到全身即時控制。

程式碼範例

以下用一個離散雙積分器系統(狀態是位置與速度,控制輸入是加速度,是移動平台速度控制、或關節角速度控制的簡化模型)示範線性 MPC 的凝聚形式與遞推時域迴圈:

python
import numpy as np
from scipy.optimize import minimize


def build_prediction_matrices(A, B, N):
    """建立凝聚形式:x_traj = Sx @ x0 + Su @ u_seq"""
    n, m = A.shape[0], B.shape[1]
    Sx = np.zeros((n * N, n))
    Su = np.zeros((n * N, m * N))

    A_power = np.eye(n)
    for i in range(N):
        A_power = A_power @ A
        Sx[i * n:(i + 1) * n, :] = A_power

    for i in range(N):
        for j in range(i + 1):
            A_power_ij = np.linalg.matrix_power(A, i - j)
            Su[i * n:(i + 1) * n, j * m:(j + 1) * m] = A_power_ij @ B

    return Sx, Su


def mpc_step(x0, x_ref, A, B, Q, R, N, u_min, u_max):
    """解一步 MPC:回傳這一步該執行的控制輸入(時域內第一步)"""
    n, m = A.shape[0], B.shape[1]
    Sx, Su = build_prediction_matrices(A, B, N)

    Q_bar = np.kron(np.eye(N), Q)
    R_bar = np.kron(np.eye(N), R)
    x_ref_bar = np.tile(x_ref, N)

    def cost(u_flat):
        x_traj = Sx @ x0 + Su @ u_flat
        state_error = x_traj - x_ref_bar
        return state_error @ Q_bar @ state_error + u_flat @ R_bar @ u_flat

    bounds = [(u_min, u_max)] * (m * N)
    u0_guess = np.zeros(m * N)
    result = minimize(cost, u0_guess, bounds=bounds, method="SLSQP")

    u_seq = result.x.reshape(N, m)
    return u_seq[0]  # 遞推時域:只執行第一步


def simulate_receding_horizon(A, B, Q, R, N, x0, x_ref, u_min, u_max, steps):
    """完整遞推時域模擬迴圈"""
    x = x0.copy()
    trajectory = [x.copy()]
    for _ in range(steps):
        u = mpc_step(x, x_ref, A, B, Q, R, N, u_min, u_max)
        x = A @ x + B @ u
        trajectory.append(x.copy())
    return np.array(trajectory)


# 雙積分器範例:狀態 [位置, 速度],控制輸入 [加速度]
dt = 0.1
A = np.array([[1, dt], [0, 1]])
B = np.array([[0.5 * dt ** 2], [dt]])
Q = np.diag([10.0, 1.0])
R = np.array([[0.1]])

traj = simulate_receding_horizon(
    A, B, Q, R, N=10,
    x0=np.array([0.0, 0.0]),
    x_ref=np.array([5.0, 0.0]),
    u_min=-2.0, u_max=2.0,
    steps=50,
)

這裡刻意用 scipy.optimize.minimize 而不是專門的 QP 求解器,是為了讓程式碼保持在標準函式庫範圍內、方便直接執行理解流程;正式的即時控制系統會改用專門的 QP 求解器(例如 OSQP、qpOASES),對凝聚後的標準 QP 形式,這些求解器的求解速度比通用非線性最佳化器快上一到兩個數量級,這個差異在需要達到百 Hz 等級控制頻率的應用裡是決定性的。

常見錯誤

  • 時域長度沒有跟控制頻率一起考慮:時域拉長能提升前瞻性,但求解時間也隨之增加,在即時控制迴路裡沒有先確認求解時間在控制週期預算內,會直接導致控制迴路跟不上。
  • 終端成本 QfQ_f 設得跟 QQ 一樣、或乾脆省略:沒有適當放大的終端成本,遞推時域策略的穩定性保證會變弱,系統可能在時域邊界附近出現不必要的震盪傾向。
  • 拿線性 MPC 直接套用在高度非線性的系統上、卻不做任何重新線性化:只在初始工作點線性化一次,系統狀態偏離工作點越遠,模型誤差累積越明顯,通常需要在每個控制週期用目前狀態重新線性化(形成一種簡化版的非線性 MPC),而不是全程套用同一組固定的 AABB
  • QQRR 當成獨立於系統物理單位的抽象數字亂調:狀態向量裡不同分量的物理單位差異很大(例如角度用弧度、角速度用弧度/秒),QQ 矩陣對角線的相對大小需要考慮這些單位差異,否則某個分量的誤差在數值上天生就比另一個分量大,會不成比例地主導最佳化結果。
  • 忽略 QP 求解器可能回傳不可行(infeasible)的情況:當約束條件互相矛盾(例如目前狀態已經逼近極限、時域內物理上不可能同時滿足所有狀態與控制限制)時,求解器可能無解,沒有處理這種邊界情況的容錯機制(例如放寬部分軟約束),控制迴路會直接卡死。

延伸閱讀

常見問題

faq_01.log
MPC 跟 PID 最本質的差異是什麼?不只是「比較複雜」而已嗎?
本質差異在於 PID 是純粹的反饋控制器——它只看「現在」的誤差(以及誤差的歷史累積與變化率),完全不知道系統未來會怎麼演變,也不知道致動器有沒有極限;MPC 則是在每一步都用系統模型「預測」未來一段時間(時域)的行為,然後解一個最佳化問題找出讓未來這段時間內成本最小的控制序列,只執行第一步就重新預測。這個差異帶來兩個 PID 結構上做不到的能力:一是原生處理多輸入多輸出之間的耦合(例如四軸飛行器四個馬達轉速互相耦合影響姿態與高度,PID 通常要拆成多個獨立迴圈硬湊,MPC 可以在同一個最佳化問題裡處理耦合關係),二是原生把致動器極限與狀態限制寫進最佳化問題的約束條件,而不是像 PID 那樣要另外加飽和限幅這種事後補救機制。
faq_02.log
時域(horizon)越長,控制效果一定越好嗎?
不一定,而且有明顯的 trade-off。時域越長,控制器能「看得越遠」,理論上越接近全時域最佳解,穩定性保證通常也更好;但代價是每一步都要解一個規模隨時域長度增大的二次規劃問題,計算成本隨時域長度大致呈平方到立方成長(取決於是否用凝聚形式),在需要毫秒等級即時反應的控制迴路裡,時域拉太長可能導致單步求解時間超過控制週期,直接讓即時性破功。實務上時域長度是一個需要跟控制頻率一起權衡的超參數,太短會讓控制器變得短視(跟只看一步的貪婪法差異不大,可能在需要提前煞車的情境下反應不及),太長則犧牲即時性,通常會先抓一個大約覆蓋系統主要動態時間常數的時域長度,再依實際運算預算調整。
faq_03.log
非線性 MPC(NMPC)什麼時候才是必要的,用線性 MPC 硬做行不行?
如果系統動態在你關心的工作範圍內非線性程度不高,或者可以在每個控制週期附近做線性化近似(例如足式機器人在小幅擾動附近的姿態控制),線性 MPC 配合適當頻率的重新線性化通常已經夠用,計算成本也遠低於真正的非線性 MPC。但像四足或雙足機器人的動態步態規劃、需要大幅度姿態變化的機動動作,系統動態(尤其是接觸力學與轉動慣性耦合)的非線性程度高到線性近似會產生顯著誤差,這種情況才真正需要非線性 MPC(通常用序列凸規劃 SCP、iLQR 或 DDP 這類方法求解),代價是計算成本高出一個數量級以上,這也是為什麼直到近幾年 GPU/嵌入式運算能力提升,NMPC 才逐漸在腿式機器人的即時控制迴路裡變得可行。