2. 二阶系统MPC无约束控制
任务目标
对如下二阶线性时不变系统设计无约束模型预测控制器(MPC),实现状态从非零初值调节至原点,并对比不同权重矩阵对闭环性能的影响:
x(k+1) = A x(k) + B u(k)
A = [1.0 0.1; -0.2 1.0]
B = [0.0; 0.1]
系统采样周期取1s,状态约束与输入约束均不设置(无约束MPC)。预测时域 N=10,控制时域与预测时域相同。通过修改状态权重矩阵 Q 与输入权重 R,观察状态轨迹与输入序列的变化。
核心原理
无约束MPC在每个采样时刻求解如下有限时域最优控制问题:
min Σ_{k=0}^{N-1} ( x_k^T Q x_k + u_k^T R u_k ) + x_N^T P x_N
s.t. x_{k+1} = A x_k + B u_k
由于无约束且目标函数为二次型,该问题可转化为线性二次调节器(LQR)问题的有限时域形式,通过求解离散Riccati差分方程获得最优反馈增益序列。实际实现中,可直接使用矩阵运算求解该二次规划(QP),或利用LQR终端代价矩阵 P 近似无限时域解。本案例采用直接求解QP的方式,在每个采样时刻基于当前状态重新优化。
环境依赖
需要 Python 3.8+ 及以下库:
numpy, scipy, matplotlib
安装命令:pip install numpy scipy matplotlib
完整实现代码
import numpy as np
import matplotlib.pyplot as plt
from scipy.linalg import solve_discrete_are
# 系统矩阵
A = np.array([[1.0, 0.1], [-0.2, 1.0]])
B = np.array([[0.0], [0.1]])
nx = A.shape[0]
nu = B.shape[1]
# 预测时域
N = 10
def mpc_control(x0, Q, R):
"""求解无约束MPC,返回最优输入序列和状态轨迹"""
# 构建QP矩阵(无约束时可直接求解)
# 使用LQR终端代价矩阵P(由离散Riccati方程求得)
P = solve_discrete_are(A, B, Q, R)
# 构建预测模型矩阵(批量形式)
# 状态预测: X = Phi*x0 + Gamma*U
Phi = np.zeros((N*nx, nx))
Gamma = np.zeros((N*nx, N*nu))
# 构建Phi
A_pow = np.eye(nx)
for i in range(N):
Phi[i*nx:(i+1)*nx, :] = A_pow
A_pow = A_pow @ A
# 构建Gamma
for i in range(N):
A_pow = np.eye(nx)
for j in range(i+1):
if i-j > 0:
A_pow = A_pow @ A
elif i-j == 0:
A_pow = np.eye(nx)
else:
break
# 实际计算: Gamma[i] = A^(i)*B + A^(i-1)*B + ... + B
# 简化计算方式
Gamma_block = np.zeros((nx, N*nu))
for j in range(i+1):
A_j = np.linalg.matrix_power(A, i-j)
Gamma_block[:, j*nu:(j+1)*nu] = A_j @ B
Gamma[i*nx:(i+1)*nx, :] = Gamma_block
# 构建代价矩阵
Q_bar = np.kron(np.eye(N), Q)
# 终端代价加入最后一步
Q_bar[(N-1)*nx:, (N-1)*nx:] += P
R_bar = np.kron(np.eye(N), R)
# 无约束最优解: U* = - (Gamma^T Q_bar Gamma + R_bar)^(-1) Gamma^T Q_bar Phi x0
H = Gamma.T @ Q_bar @ Gamma + R_bar
F = Gamma.T @ Q_bar @ Phi
# 最优输入序列
U_opt = -np.linalg.solve(H, F @ x0)
# 计算状态轨迹
X_pred = Phi @ x0 + Gamma @ U_opt
return U_opt, X_pred
def simulate_closed_loop(x0, Q, R, steps=20):
"""闭环仿真"""
x = x0.copy()
x_history = [x.copy()]
u_history = []
for _ in range(steps):
U_opt, _ = mpc_control(x, Q, R)
u = U_opt[0:nu] # 只取第一步
u_history.append(u.copy())
x = A @ x + B @ u
x_history.append(x.copy())
return np.array(x_history), np.array(u_history)
# ============ 仿真与对比 ============
x0 = np.array([2.0, -1.0])
steps = 30
# 权重矩阵设置
configs = [
{"name": "Q=I, R=1", "Q": np.eye(nx), "R": np.array([[1.0]])},
{"name": "Q=10I, R=1", "Q": 10*np.eye(nx), "R": np.array([[1.0]])},
{"name": "Q=I, R=0.1", "Q": np.eye(nx), "R": np.array([[0.1]])},
]
plt.figure(figsize=(12, 8))
colors = ['b', 'r', 'g']
for idx, cfg in enumerate(configs):
x_hist, u_hist = simulate_closed_loop(x0, cfg["Q"], cfg["R"], steps)
plt.subplot(2, 2, 1)
plt.plot(x_hist[:, 0], label=cfg["name"], color=colors[idx])
plt.xlabel("Time step")
plt.ylabel("x1")
plt.legend()
plt.grid(True)
plt.subplot(2, 2, 2)
plt.plot(x_hist[:, 1], label=cfg["name"], color=colors[idx])
plt.xlabel("Time step")
plt.ylabel("x2")
plt.legend()
plt.grid(True)
plt.subplot(2, 2, 3)
plt.plot(u_hist[:, 0], label=cfg["name"], color=colors[idx])
plt.xlabel("Time step")
plt.ylabel("u")
plt.legend()
plt.grid(True)
plt.subplot(2, 2, 4)
# 绘制相轨迹
for idx, cfg in enumerate(configs):
x_hist, _ = simulate_closed_loop(x0, cfg["Q"], cfg["R"], steps)
plt.plot(x_hist[:, 0], x_hist[:, 1], label=cfg["name"], color=colors[idx])
plt.xlabel("x1")
plt.ylabel("x2")
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()
# 打印稳态误差(最终状态范数)
print("=== 闭环性能对比(30步后) ===")
for idx, cfg in enumerate(configs):
x_hist, _ = simulate_closed_loop(x0, cfg["Q"], cfg["R"], steps)
final_norm = np.linalg.norm(x_hist[-1])
print(f"{cfg['name']}: 最终状态范数 = {final_norm:.4f}")
运行与验证
运行方式:将上述代码保存为 mpc_second_order.py,在终端执行 python mpc_second_order.py。
预期输出:程序将显示4个子图:状态 x1、x2 的时间响应曲线,控制输入 u 的序列,以及状态相平面轨迹。控制台输出三种权重配置下30步后的状态范数。
通过条件:
- 所有状态轨迹均收敛至原点附近(最终范数 < 0.05)
- 增大 Q(状态权重)时,状态收敛更快但输入幅值更大
- 减小 R(输入权重)时,控制作用更强,状态调节更迅速
- 相轨迹平滑趋近原点,无振荡发散
关键说明
本实现采用批量QP求解方式,在每个采样时刻基于当前状态重新计算最优输入序列,并仅施加第一步控制量(滚动时域控制)。终端代价矩阵 P 通过求解离散代数Riccati方程获得,用于近似无限时域最优性,保证闭环稳定性。代码中 mpc_control 函数返回完整预测序列,simulate_closed_loop 执行闭环仿真并记录状态与输入历史。