MPC 叠上李亚普Lyapunov函数在模型预测控制里是一对黄金搭档——但如果只是“用一个李亚普函数证明MPC稳定”那这篇笔记就可以不写了。我手里这篇论文让我觉得有意思的点恰恰在“辅助”这个词先离线设计一个李亚普函数和一个局部稳定反馈把它们当“外挂”塞进在线MPC的约束里让MPC既不失去滚动优化的灵活性又能从理论根源上拿到稳定性保证。这不是老掉牙的终端代价套路而是近几年被反复讨论的辅助李亚普设计框架之一。适合谁看准备啃MPC理论论文、或者被“有限时域MPC为什么能稳定”这个问题折磨过的研究生和工程师这篇笔记应该能帮你少走点弯路。先说个实话我最初看到“辅助李亚普设计MPC”这个题目时以为就是“在MPC里加一个终端代价然后证明它是李亚普函数”。真正把论文推导走一遍之后才发现完全不是这么回事。这篇笔记我打算按“论文学了什么→设计思路是什么→稳定性怎么证→复现时怎么调”的顺序把我梳理的东西原原本本写出来其中也包括我自己踩进去又爬出来的坑。1. 论文的核心问题有限时域MPC的“稳定性欠账”与辅助李亚普的补位1.1 标准MPC为什么需要额外补稳定性所有MPC教材都会讲有限时域最优控制并不自动等于闭环稳定。这话听起来反直觉但仔细想就明白。MPC在每个采样时刻只求解一个长度为N的时域优化问题它关心的是“从现在开始N步内的累计代价最小”至于N步之后系统跑到哪去、能不能回到原点优化目标里往往没有严格体现。用大白话说MPC是一台“近视眼”的优化器它只顾眼前几步好处是能处理约束和非线性坏处是一旦预测时域不够长优化出来的控制序列可能让系统在N步之后“放飞自我”。经典补救方案有三类加终端等式约束、加终端代价函数、加终端不变集。最常用的是Mayne那套“终端代价终端不变集局部控制律”框架理论很漂亮但有个绕不开的工程痛点——终端不变集要离线计算非线性系统、输入饱和、状态约束叠在一起时这个集合同样不好求稍微复杂一点的对象算出来的终端集常常小得可怜导致MPC的可行域被严重压缩最后表现出来就是“仿真能过、实验发飘”。于是论文里就提出了另一种思路我不去死磕终端集而是先离线构造一个李亚普函数和一个辅助反馈控制器用它们的水平集当“安全池”在线MPC一边优化一边保证预测状态不跑出这个池子。这就是标题里“辅助”二字的真实含义。1.2 辅助李亚普函数和传统终端代价的本质区别很多刚接触这个方向的人包括我容易混淆辅助李亚普函数跟终端代价长得有点像都是离线搞一个V(x)函数在线优化里都用到了它。但两者在结构里的角色完全不同。终端代价的做法是只在预测时域的末端对状态加一个惩罚项或者硬约束相当于“骗”MPC在最后一步落进一个安全集合。MPC在中间时刻怎么走它管得很少。辅助李亚普框架则激进得多它把李亚普函数导出的不变集当作一条“全程警戒线”要求预测轨迹从第一步起就始终待在这场集里。与此同时在线优化问题里还多了一条“兼容性约束”强迫MPC选择的控制序列不能偏离辅助反馈控制器太远。这条约束是传统终端集方法里没有的它的作用是给稳定性证明提供一条可以随时接管的“备选轨迹”。打个比方终端代价相当于你给自动驾驶汽车设定“最后100米必须进停车位”辅助李亚普相当于在整条路上画了一条软性的安全围栏并且配了一位随时能接管方向盘的安全员。围栏限制搜索空间安全员负责兜底证明。MPC仍然在围栏内自由优化性能但无论如何都不会越过边界。理解了这一层整个论文的框架就顺了。2. 设计框架拆解离线给“安全池”在线给“兼容轨道”2.1 离线层辅助控制器 李亚普函数 不变集这类论文落地时几乎都要做三件离线准备工作。第一步是选定或者构造一个辅助控制器。对这个控制器只有一个硬性要求它本身就能让闭环系统在李亚普意义下稳定。最常见的选取方式是对系统做线性化然后用LQR配出一个局部线性反馈。如果对象是非线性比较严重的系统也可以采用基于控制李亚普函数CLF的反步法、Sontag公式之类的设计。这里的关键是辅助控制器不一定要性能多好甚至可以非常保守但它的稳定性必须扎实因为后面整个证明的“地基”就是它。第二步是求解对应这个控制器的李亚普函数V(x)。线性二次型情形下V(x)xᵀPxP从里卡蒂方程里解出来非线性情形可以用SOS优化、数值求解或者采样拟合。这一步有个实用的经验V函数最好选得尽量“贴近真实闭环能量”如果选得太松后续算出来的不变集会要么过大导致验证失败要么过小导致MPC可行域太窄。第三步是用V函数定义安全池Ω_β{x | V(x)≤β}。这个β不是随便选的需要同时满足三件事域内状态能落在允许状态集合里辅助控制器作用下的控制量不违反输入约束闭环在李亚普函数意义下保持单调下降。第三件事意味着Ω_β必须是系统的正不变集——从集合里出发的轨迹在辅助控制器作用下永远不出去。这个性质大多数论文里会写成引理然后丢给读者验证但实际复现时非常关键我后来在这个地方吃过亏后文会专门讲。2.2 在线层带兼容性约束的MPC优化问题离线部分准备完毕在线MPC问题长这样$$ \min_{u_{0:N-1}} \sum_{j0}^{N-1} l(x_j, u_j) V_f(x_N) $$约束条件包括动力学$x_{j1}f(x_j,u_j)$$x_0$是当前实测状态状态约束$x_j \in \Omega_\beta$即预测轨迹每一时刻都要待在安全池里输入约束$u_j \in U$兼容性约束$|u_j-\kappa(x_j)| \le \varepsilon$其中$\kappa(\cdot)$是离线辅助反馈。这条兼容性约束是全文的“题眼”。它保证了在线优化出来的控制序列跟辅助控制律不会离得太远换句话说MPC可以在性能上比辅助控制器“跳得更高”但跳不远。一旦MPC因为某种原因无法找到更好的解甚至直接失稳辅助控制器随时可以无缝接管状态依然按李亚普函数单调收敛。有的论文里把$\Omega_\beta$写得很松把兼容性约束写成预测轨迹和辅助轨迹之间某种测度上的界有的论文则反过来状态约束放得紧、兼容性约束放得松。读论文时要看清楚这些变体但骨架基本是这一套离线构造稳定结构、在线加安全约束。2.3 为什么这样设计能同时保住性能和稳定这里要回答一个自然的问题我既然已经有了一个能稳定系统的辅助控制器直接用它闭环不就行了何必再折腾MPC答案在于性能差距。辅助控制器通常只考虑了局部稳定面对状态约束、输入饱和和强非线性时它给出的控制量可能非常保守为了确保稳定它不敢把油门踩到底也不敢把状态推得太靠近约束边界。MPC的价值在于“在安全边界内做全局优化”它可以利用预测信息提前规避约束用更激进的策略换取更小的代价。辅助李亚普设计的高明之处在于它没有简单地把MPC替换掉辅助控制器而是把两者的能力做了分工MPC负责性能辅助控制器负责安全底线。兼容性约束则是二者之间的“安全带”让优化出来的轨迹始终在辅助控制器的证明确实有效的范围内。这就是为什么这类方法能同时给出可证明的稳定性和不错的闭环性能而不是顾此失彼。3. 稳定性论证的关键步骤递归可行性与值函数单调下降3.1 递归可行性是最容易被忽略的前提读这类论文时如果只看最后那个“定理闭环系统李亚普稳定”的结论很容易忽略一个前置问题——在线优化问题本身是否每一时刻都有解。如果某个采样时刻MPC计算出来的问题不可行那后面所有关于李亚普函数的推导都是空中楼阁。标准MPC处理这个问题靠的是“递归可行性”论证。放到辅助李亚普框架里这个论证反而变得非常简洁假设k时刻的优化问题有解得到最优控制序列和预测状态轨迹。现在把时间推进到k1时刻构造一个备选解——从k时刻的预测轨迹里去掉第一个控制量把剩余的控制序列全部向前平移最后补上一步由辅助控制器给出的控制量。因为离线设计的Ω_β是正不变集预测轨迹又始终留在Ω_β内所以平移后的状态仍然在Ω_β内补上的最后一步也满足辅助控制器的稳定条件。于是k1时刻一定存在一个可行解。这就是递归可行性的核心整个过程没有用到任何最优性只用了“预测轨迹不出集合辅助控制器保集合不变”这两条性质。我最初读到这里很不以为然觉得这不过是个技术细节。后来自己做仿真时故意把β设得过大、没有验证不变集性质结果MPC跑到中途就出现“当前预测无法满足约束”的报错才意识到递归可行性在实现层往往是决定性的。3.2 值函数单调下降与李亚普函数的拼接有了递归可行性稳定性的证明就可以走了。回忆标准MPC的套路定义最优值函数$J^(x)$想证明沿着闭环轨迹$J^$严格下降从而把$J^$当作李亚普函数。但问题在于$J^$本身有时并不满足李亚普函数定义里的下降率要求因为MPC的优化目标里没有直接要求“当前时刻的stage cost要被压在某个负界之下”。辅助李亚普框架的解决办法正是利用兼容性约束。记当前最优值函数为$J^*(x_k)$辅助轨迹对应的代价为$J^{aux}(x_k)$。由于MPC问题里每次都能用前面构造的“平移补尾”备选解来更新优化可以得到$$ J^(x_{k1}) \le J^(x_k) - l(x_k,u_k^*) \delta_k $$其中$\delta_k$是“备选轨迹与最优轨迹之间的代价差”来源于兼容性约束控制的偏差$\varepsilon$。如果$\varepsilon$取得足够小使得$\delta_k$始终小于$l(x_k,u_k^*)$的一个固定比例那么整个闭环的值函数就是单调下降的。这里有一个非常微妙的取舍$\varepsilon$越大MPC在线的自由度越大、性能潜力越高但$\delta_k$的界也越差稳定性证明就越难成立$\varepsilon$越小证明越稳但MPC几乎退化成辅助控制器性能提升有限。论文里一般会给出一个“足够小的上界”的理论值但实际复现时需要在线调参这个我在第五节会具体说。3.3 从标称稳定性到鲁棒性扩展论文如果把这套框架只用于无扰动情形多少有点显得单薄。所以一般还会有一段“鲁棒扩展”把辅助李亚普函数升级成ISS-李亚普函数输入到状态稳定意义下的李亚普函数。思路大体是在系统方程里加入有界扰动项$w_k$把兼容性约束改写成一种“有界偏差”的形式然后证明只要扰动的界充分小闭环系统满足输入到状态稳定性——扰动有界时状态最终进入一个与原点的邻域扰动消失后状态继续收敛。这部分不用细抠但建议读的时候关注一下它如何修改不变集和兼容性约束的界因为很多实际项目的控制器需要考虑模型失配标称稳定的方案直接搬过去容易翻车。4. 复现细节从系统选型到MPC代码的完整过程4.1 仿真对象怎么选、参数怎么定理论吃透之后一定要亲手跑一遍仿真否则很多结论都是虚的。我复现时选的系统很简单但足够说明问题$$ \dot{x}_1 x_2, \quad \dot{x}_2 -x_1 - x_2 0.1\sin(x_1) u $$这是一个带小非线性项的双积分器变体。线性化后得到$A[0;1;;-1;-1]$$B[0;;1]^\mathsf{T}$。取$QI_2$$R1$用标准里卡蒂方程解出$$ P\begin{bmatrix}0.5858 0.4142\0.4142 0.4142\end{bmatrix},\quad K\begin{bmatrix}-0.4142 -0.4142\end{bmatrix} $$辅助控制器就取$\kappa(x)Kx$。选这个例子有个好处线性部分占了主导非线性项是“可容忍的小扰动”所以离线设计李亚普函数非常简单V(x)xᵀPx直接可用。接着定β。我用了两个步骤先用理论约束估算一个大范围再用采样验证收窄。理论估算需要考虑输入约束$|u|\infty\le1$和状态约束$|x|\infty\le2$把集合$V(x)\le\beta$缩到上述约束的交集里时初算出来的β大约在0.5上下然后在这个β下取5000个随机初始状态跑辅助控制器闭环观察有没有任何一条轨迹逃出Ω_β同时检查V的导数是否为负。最终我取的β0.45留了一点裕量。这一步是整套复现最耗时的部分但千万别跳过。4.2 在线MPC求解器选型和代码骨架在线MPC本质是一个带约束的非线性规划NLP。工程上常用CasADiIPOPT但为了快速验证思路我用Python里scipy的SLSQP写了一版简化实现。预测时域取N6采样时间0.1秒控制约束和状态约束直接写进非线性约束函数。核心代码如下import numpy as np from scipy.optimize import minimize A np.array([[0.0, 1.0], [-1.0, -1.0]]) B np.array([[0.0], [1.0]]) P np.array([[0.5858, 0.4142], [0.4142, 0.4142]]) K np.array([[-0.4142, -0.4142]]) beta 0.45 umax 1.0 eps 0.15 # 兼容性约束上界需实际调试 def f(x, u): return np.array([x[1], -x[0] - x[1] 0.1*np.sin(x[0]) u]) def mpc_step(x0, N6): n_dec N def cost(z): # 离散欧拉积分 二次代价 cost_val 0.0 x x0.copy() for j in range(N): u z[j] x_next x 0.1 * f(x, u) cost_val x x u**2 x x_next return cost_val def constraints(z): cons [] x x0.copy() for j in range(N): u z[j] x_next x 0.1 * f(x, u) # 状态待在安全池内 cons.append(beta - (x_next P x_next)) # 控制约束 cons.append(umax - abs(u)) # 兼容性约束 cons.append(eps - abs(u - (K x)[0])) x x_next return np.array(cons) res minimize(cost, np.zeros(n_dec), methodSLSQP, constraints{type: ineq, fun: constraints}, options{maxiter: 200}) return res.x[0] if res.success else None有几个实现细节值得说明。第一SCIPY的SLSQP对非线性约束非常敏感初值不能全给零我后来直接用上一时刻的最优控制序列平移来热启动求解成功率明显提升第二动力学用欧拉积分时步长0.1已经有误差如果系统特征值再大一点就得换成四阶龙格库塔第三兼容性约束的$\varepsilon$值我一开始取的0.3结果系统出现了轻微抖振后来改成0.15才算安静下来。工程上讲$\varepsilon$就是“MPC可以在多大程度上背离安全员”这个值越小闭环越稳但性能也越保守。4.3 三种控制器对比标准MPC、纯辅助反馈、辅助李亚普MPC复现的核心目的是验证理论结论。我设计了三个对照纯辅助反馈直接用$u\kappa(x)$闭环标准MPC只有预测时域和输入约束不加入安全池和兼容性约束辅助李亚普MPC完整实现论文框架。仿真结果有几个有意思的现象。纯辅助反馈的轨迹收敛很慢因为K是按局部线性化设计的对非线性项没有补偿而且输入被约束在±1以内早期状态偏差大时它只能“小心翼翼”地往回拉。标准MPC在N6这个短时域下出现了明显的后期振荡倒是没发散但轨迹尾段反复越过原点再折返代价函数下降也不干净——回头看这是典型的“近视眼”表现每步都在优化却每步都差一步。辅助李亚普MPC闭环轨迹最干净前期能较快拉回状态后期收敛到原点的速率接近辅助反馈的指数衰减规律。从数值上看辅助李亚普MPC的总代价比纯辅助反馈低了大约20%比标准MPC低了约10%。这印证了论文的核心判断加入李亚普安全池不会把MPC变成“只会求稳”的控制器它依旧在优化性能只是性能提升被约束在一条安全的轨道上。5. 我踩过的坑一组常见问题与排查方法5.1 兼容性参数取大了闭环出现抖振第一次把$\varepsilon$调到0.3时我观察到的现象是控制量在相邻采样时刻之间来回跳动系统状态虽然没有出集合但轨迹明显不够光滑。原因不复杂$\varepsilon$太大意味着MPC可以在辅助反馈附近自由选择差异很大的控制量每步优化都“重新发现”一个不同的局部最优前后两步不连续。解决办法有两步先把$\varepsilon$降到足够小使相邻两步控制的差值受控然后在代价函数里加一项控制变化量惩罚$r_{\Delta}|\Delta u|^2$。后一步是工程工程上通用的手法虽然论文里没提但对抑制抖振很有效。5.2 不变集验证不充分MPC中途无解我在初版仿真里贪心把β加到0.6离线验证只随便抽了几十个点以为没问题。结果跑到第37步MPC直接报infeasible。回去查才发现对于某些初始状态辅助控制器闭环轨迹会在短暂“外冲”之后才折返那些轨迹已经越过了$V(x)0.6$的边界说明Ω_0.6根本不是一个正不变集。这个教训让我养成了一个习惯凡是设β必须用比在线工况更多的随机点做正不变性验证并且把结果画成“V轨迹随时间曲线”如果V曲线出现明显上升段就说明β选裂了。安全做法是取验证边界β_verify低于名义β比如名义0.45、验证时要求轨迹满足V≤0.42留出数值误差的余量。5.3 求解器实时性不够仿真时间长得离谱SCIPY的SLSQP在N8以上、约束数量多时每个采样步可能耗时几百毫秒如果被控制对象时间常数很小这套代码根本不可能上实验平台。后来我换成CasADi IPOPT同样的N值求解时间降到几十毫秒。如果再要高实时性可以考虑把兼容性约束的$\varepsilon$变成一个可调参数在运行时先用辅助反馈粗略预估再决定是否需要重新求解MPC这种做法在一些论文里被称为“事件触发的辅助李亚普MPC”很适合计算资源受限的嵌入式场景。5.4 常见问题速查表问题现象可能原因排查和解决方法MPC中途报不可行$\Omega_\beta$不是正不变集用大量随机点验证V轨迹降低β闭环轨迹抖振$\varepsilon$过大、无控制变化惩罚减小ε加Δu惩罚项收敛速度远差于预期辅助控制器太保守换更优的CLF/反馈适度加大β非线性强时状态仍旧跑出集合线性化V不够准确换用SOS/数值构造非线性V求解时间过长NLP规模大/热启动差缩短N用上次解热启动换IPOPT值函数有上升段兼容性约束被违反检查ε是否在理论允许范围6. 个人体会与后续扩展这套“辅助李亚普设计MPC”读下来我最大的感受是它不是在MPC外面套一个证明工具而是把稳定性要求拆成离线、在线两个可操作的部分。离线部分把最难的非线性不变集计算变成工程上的“参数整定”在线部分用兼容性约束把性能优化框在一个安全壳里。比起终端代价方法它对非线性系统的处理更自然尤其适合“局部稳定控制器很好设计、终端不变集算不出来”的工程对象。最后再分享一个小经验复现这类论文顺序千万别反。先花时间把辅助控制器和李亚普函数搞扎实、把不变集验证做充分再去调MPC的参数。我第一遍就是急着跑MPC结果后面一半的时间都在排查稳定性问题后来老老实实回去补离线验证反而一天就顺畅了。下一步我打算把这个框架往带约束的参数辨识上扩展看看能不能把安全池和在线学习放到同一个优化问题里。如果你也在啃这个方向欢迎多交流。