插电式混合动力车辆(PHEV)的能源管理,说穿了就是一句话:每一时刻驱动系统需要的功率到底谁来出,发动机出多少、电机出多少、电池往里充还是往外放。但这个“一句话问题”落到实际工程里,牵扯出来的东西相当多——发动机万有特性是非线性的、电池SOC有硬边界、电机效率随工况变化、驾驶员需求功率还在实时波动。我去年做了一套基于Matlab的能量管理方案,核心是用凸优化建模,MPC做滚动优化,再用ADMM把在线求解的时延压到毫秒级。这篇文章把整个思路、数学原理、代码结构和踩过的坑都说一遍,适合正在做PHEV/HEV能量管理、或者对凸优化和MPC结合感兴趣的研究生和工程师参考。
1. 先把问题说清楚:PHEV能量管理到底在优化什么
1.1 动力系统结构与功率流
插电式混合动力车辆的结构可以简单分成串联、并联和混联几种,我在项目里用的是并联构型。并联构型里发动机和电机都通过机械路径连接到驱动轴,两者输出的转矩叠加后经过变速器驱动车轮。这种拓扑最大的特点是:发动机和电机的功率可以按任意比例分配,电池既能提供助力也能回收制动能量。
写代码之前,我先列出一组核心方程:
- 需求功率:(P_{req}(t)=v(t)\cdot\left(mg f+0.5\rho_a C_d A_f v^2(t)+m\delta a(t)\right)/1000),单位kW,其中(f)是滚动阻力系数,(\delta)是旋转质量换算系数。
- 功率平衡:(P_{req}(t)=P_{ice}(t)+P_{mot}(t)),即发动机和电机的功率之和等于驱动需求。
- 电池功率:(P_{bat}(t)=P_{mot}(t)/\eta_{mot})(电动工况),(P_{bat}(t)=P_{mot}(t)\cdot\eta_{mot})(发电工况)。
这些方程看似基础,但它们决定了整个优化问题最底层的耦合关系。发动机、电机、电池三个子系统通过功率平衡互相牵制,而SOC状态又通过电池功率的积分和未来时刻的驱动能力挂钩。
1.2 优化目标与硬约束
PHEV能量管理优化目标一般有两层,一层是经济性,另一层是排放或驾驶性,但工程上最核心的还是等效燃油消耗最小。单独追求发动机燃油消耗最低会导致电池SOC迅速耗尽,所以目标函数里必须加入电能的“代价”。
我的做法是用等效因子法:
[ J=\sum_{k=0}^{N-1}\left(\dot{m}f(k)\cdot T_s+s_e\cdot P{bat}(k)\cdot T_s\right) ]
其中(\dot{m}f)是瞬时燃油消耗率,(s_e)是电能等效燃油系数,(P{bat})是电池输出功率。这个目标函数把“电”和“油”统一到了同一个量纲下,后面凸化处理就顺理成章了。
约束条件包括以下几类:
- 发动机功率范围:(P_{ice,min}\le P_{ice}\le P_{ice,max}),还附带转速、转矩的MAP限制。
- 电机功率范围:(P_{mot,min}\le P_{mot}\le P_{mot,max})。
- 电池SOC边界:(SOC_{min}\le SOC\le SOC_{max}),以及终端SOC约束,防止“用完即弃”。
- 功率平衡等式约束。
1.3 这问题的难点在哪里
难点之一是发动机燃油消耗率是高度非线性的MAP,电机效率同样是二维查表函数,电池开路电压还随SOC变化,直接扔给通用非线性优化器,解起来既慢又不稳定。难点之二是实时性,车载ECU的算力远不如一台高性能PC,不可能在每个采样周期都调用重型优化算法。难点之三是预测的不确定性,驾驶员下一脚油门踩多深根本不知道,控制器只能基于有限时域做局部决策。
理解了这三层困难,才能理解后面为什么要走“凸优化建模+MPC滚动+ADMM加速”这条路。每一层都是在解决某一个具体瓶颈。
2. 为什么要用MPC:从后验最优到滚动优化
2.1 离线全局优化为什么不能直接用
很多刚接触能量管理的同学,第一反应是“既然要最小化整个工况的燃油消耗,那直接做动态规划(DP)或者全局寻优不就行了?”理论上确实可以,动态规划在已知完整工况的前提下能得到全局最优解,我前期也用它做过基准对标。
但问题在于:全局优化是“事后诸葛亮”。它需要知道未来整个行驶工况的速度轨迹,这在真实驾驶场景里是做不到的。而且DP的计算量和状态网格数呈指数关系,工况一长,计算时间就是指数爆炸。更麻烦的是,DP解出来的是开环控制序列,一旦实际工况和预测轨迹有偏差,这个解立刻失效。所以说,全局优化只能作为离线标定的参考基准,不能直接装车。
2.2 MPC的滚动时域逻辑
模型预测控制解决这个问题的方式很务实:每个控制周期只优化未来一段有限时间窗口,执行完当前步的控制量之后,下一次采样到来时把窗口向前滚动一格,再重新优化。这种“边走边看”的策略天然带有反馈校正能力,对模型误差和工况扰动有一定鲁棒性。
具体到PHEV能量管理,MPC在每个采样时刻要做的事是:
- 获取当前SOC、车速、预测的未来车速序列(可以用导航/交通预测数据,也可以简单用马尔可夫链预测)。
- 在预测时域(N_p)内求解一个有限时域最优控制问题,得到发动机功率、电机功率的最优序列。
- 只把序列中的第一步控制量施加到被控对象上。
- 下一采样时刻重复上述过程。
MPC的另外一个好处是可以显式处理约束。发动机功率限值、电机转矩限值、SOC边界、甚至发动机启停逻辑都可以写进优化问题的约束里。这一点和基于规则的能量管理策略形成鲜明对比,规则策略靠的是“IF-THEN”查表,遇到边界工况往往要么太保守要么太激进,而MPC是在约束可行域内做最优抉择。
2.3 预测模型与凸化假设
MPC的性能很大程度上取决于预测模型的精度和形式。在PHEV能量管理中,预测模型就是车辆纵向动力学加电池SOC动态。为了后面能和凸优化对接,我在模型中做了两个关键简化,这也是实际项目中常见且合理的工程近似。
第一个简化:在预测时域内,假设电池开路电压和内阻基本不变。文献和实测都表明,在SOC变化范围不大(比如±5%)时,OCV变化很小,这个假设是站得住的。第二个简化:把电机损耗和电池损耗统一近似为功率的二次函数或者分段线性凸函数。这样,整个MPC子问题就变成了一个凸二次规划或者凸分段线性规划。
这个“窄范围化”思路是整篇方法的核心,说白了就是:既然完整非线性问题解不动,那就先在一个局部可信的范围内把模型凸化,利用凸优化的强对偶性和快速求解算法来逼近全局最优。实际测下来,这个套路在绝大多数驾驶工况下和DP全局最优的差距能控制在3%以内,性价比非常高。
3. 凸优化与CVX:如何把非线性难题变成“可解问题”
3.1 凸优化为什么香
在优化领域,凸优化的地位有点像线性代数里的矩阵分解——它有一整套成熟的理论保证和高效算法。一个凸优化问题具有一个让人安心的性质:任何局部最优解都是全局最优解。这意味着求解器可以放心大胆地沿梯度方向搜索,不用怕掉进局部极值点出不来。
更重要的一点,工程意义上的“快”。凸优化有内点法、一阶法、交替方向乘子法等一大把高效求解算法,即使问题规模到几十万变量,也能在可接受时间内找到高精度解。这跟非线性规划(NLP)动不动就要靠初始点、容易数值病态形成鲜明对比。
所以把PHEV能量管理问题“折腾”成凸优化问题,本质上是在放弃一部分模型精度,换取求解的确定性、快速性和全局最优保证。这个取舍在实际工程里往往是被接受的。
3.2 非线性能源管理问题的凸化套路
把原始非凸问题变成凸问题,需要三个步骤。第一步是把目标函数凸化。发动机燃油消耗率(\dot{m}f(P{ice}))通过多项式拟合或者分段线性拟合成凸函数,我实测用二次凸函数拟合在常用功率区间内精度就足够好。对于电池电能部分,前面等效因子法已经把它变成SOC状态的线性函数,线性天然是凸的。
第二步是约束凸化。发动机、电机功率的上限和下限是线性不等式,天然凸;SOC动态方程如果离散成线性形式,那么整个等式约束也是仿射的,保持凸性。电池的内阻损耗如果以SOC状态和功率的二次型形式出现,只要二次项系数矩阵半正定,就依然是凸的。
第三步是处理不可行问题。实际工程里约束可能互相冲突,比如SOC已经很低了,驾驶员还要求大功率加速,这时原问题无解。我的做法是在目标函数里加入松弛变量,把SOC软约束化,配合适当的惩罚系数,保证优化器永远能给出一个“尽量不违反约束”的解。这个技巧在下一章CVX建模里具体展开。
3.3 CVX建模:怎么用更少代码实现凸优化
CVX是Matlab环境下最常用的凸优化建模工具,它的设计哲学就是“把数学形式直接写成代码”。用户不需要关心内部用了什么算法、矩阵怎么分解,只需要用CVX的语法定义变量、目标函数和约束,CVX会自动检测问题的凸性,然后调用后端的求解器(如SEDUMI、SDPT3,或者支持Gurobi/Mosek做接口)来完成求解。
我用CVX的典型MPC问题建模框架大概长这样:
%% 单个MPC步长的CVX求解 cvx_begin quiet variable P_ice(Np) % 发动机功率序列 variable P_bat(Np) % 电池功率序列 variable SOC(Np+1) % SOC轨迹 % 目标:燃油消耗 + 电能等效消耗 + 软约束惩罚 J = sum(m_fuel_rate(P_ice)) * Ts + ... S_e * sum(P_bat) * Ts + ... C_soft * sum(pos(SOC - SOC_max) + pos(SOC_min - SOC)); minimize(J) subject to % 动力学离散模型 SOC(1) == SOC_0; for k = 1:Np SOC(k+1) == SOC(k) - Ts/E_bat * P_bat(k); end % 功率平衡 P_ice + P_bat == P_req(1:Np); % 功率限值 P_ice_min <= P_ice <= P_ice_max; P_bat_min <= P_bat <= P_bat_max; % SOC软边界 SOC_min <= SOC <= SOC_max; cvx_end这里有一个很多人容易踩的坑:CVX的变量不能参与非凸运算。比如SOC和P_bat相乘形成双线性项,或者对功率查表插值,都会导致CVX报错或者直接判定非凸。解决办法就是前面说的,把模型在窄范围内线性化、把查表换成凸拟合函数。
另一个实战经验是:CVX里的pos()函数和松弛变量配合非常好用。pos(x)相当于max{x,0},是一个凸函数,作为软约束惩罚项加入目标函数后,既能软化约束又能保持问题凸性,非常适合处理SOC越界和功率超限的模糊边界。
求解器选择也有讲究。SDPT3和SeDuMi是CVX默认自带的求解器,通用性强,但速度一般,大规模MPC问题会有点慢。如果装了Gurobi或Mosek,可以设置cvx_solver gurobi,线性规划和二次规划问题的求解速度会明显提升,我在多步MPC测试里,Gurobi比SDPT3能快3到5倍。如果没有商业求解器授权,Academic License对高校用户通常是免费的,申请流程也不复杂。
4. ADMM的加入:让MPC在车载算力上跑得起来
4.1 ADMM的数学内核
ADMM(交替方向乘子法)是一种求解可分结构凸优化问题的一阶算法。它把一个复杂的全局问题拆分成多个相对简单的子问题,通过交替求解和更新对偶变量来逼近原问题的最优解。
考虑标准形式的凸优化问题:
[ \min_{x,z} \ f(x)+g(z) \quad \text{s.t.} \quad Ax+Bz=c ]
构造增广拉格朗日函数:
[ L_\rho(x,z,\lambda)=f(x)+g(z)+\lambda^T(Ax+Bz-c)+\frac{\rho}{2}|Ax+Bz-c|_2^2 ]
迭代步骤就三步:
- (x^{k+1}=\arg\min_x L_\rho(x,z^k,\lambda^k))
- (z^{k+1}=\arg\min_z L_\rho(x^{k+1},z,\lambda^k))
- (\lambda^{k+1}=\lambda^k+\rho(Ax^{k+1}+Bz^{k+1}-c))
这里(x)更新和(z)更新是解耦的,可以分别针对各自子问题的最简单结构求解。在很多情况下,这两个子问题都有闭式解或者非常廉价的数值解,这是ADMM快的关键。
4.2 ADMM为什么适配MPC
MPC每个采样周期要解决一个凸二次规划,如果用内点法,需要在每次迭代里求解一个大型线性方程组,矩阵分解的复杂度大约是(O(n^3))。当预测时域长、状态变量多时,这个成本相当可观。ADMM直接把这个大问题拆成了多个带有特殊结构的小问题,每步迭代的计算量大幅下降,而且迭代次数通常在中高精度需求下控制在几十次以内,实际求解时间可以从几十毫秒压缩到几毫秒。
MPC问题天然适合ADMM分解,因为它的结构就是典型的目标函数可分+约束可分。状态轨迹的动力学约束、控制量的限值约束都可以各自独立处理。我在实现里这样拆:
- (x)子问题:处理系统的动力学递推,即给定控制量序列时更新状态轨迹和SOC轨迹。这本质上是一个带线性等式约束的最小二乘问题,由于递推矩阵是稀疏带状结构,可以预计算一次矩阵分解,之后每次迭代只需要做前代回代,速度极快。
- (z)子问题:处理控制量的箱式约束和功率平衡约束耦合部分,等价于做一个带限幅的投影算子,直接把控制量截断到可行区间内。
- (\lambda)更新:就是简单的向量加法和缩放,计算量几乎可以忽略。
4.3 ADMM和CVX怎么配合
我在项目里的分工是这样的:离线阶段用CVX把MPC问题建模、验证凸性、求高精度的参考解,用这个参考解去校验ADMM实现的正确性。在线阶段则把ADMM作为实时求解器,CVX不再参与实际控制循环。简单说就是“CVX负责验证,ADMM负责上线”。
这样做还有一个额外的好处:后续如果换模型参数或者改约束条件,可以直接在CVX里快速改代码重新验证,完全不影响ADMM求解器的结构。我在调试过程中,对比CVX高精度解和ADMM迭代50次左右的结果,目标函数值的相对偏差一般在0.1%以内,这个精度对能量管理完全够用。
另一个技术细节是惩罚参数(\rho)的选取。ADMM的收敛速度对(\rho)非常敏感:(\rho)太大会导致对偶变量振荡,收敛反而慢;(\rho)太小又会让原始变量收敛极慢。经验做法是先取一个中等值,比如(\rho=10),然后根据迭代残差动态调整,类似自适应ADMM。标准化的做法是监控原始残差和对偶残差,如果两者比值长期偏离均衡,就相应调整(\rho)。
5. Matlab代码实现:CVX离线验证 + ADMM在线求解的完整流程
5.1 代码结构与数据流
整套代码我分成五个模块,用脚本文件的方式组织,方便调试和复用:
| 模块 | 文件名 | 作用 |
|---|---|---|
| 参数初始化 | init_vehicle_params.m | 整车参数、电池参数、MPC参数、ADMM参数 |
| 工况生成 | generate_drive_cycle.m | 生成WLTC/UDDS工况,输出速度时间序列 |
| 参考求解 | solve_cvx_ref.m | 用CVX离线求解给定工况的MPC问题,得到参考解 |
| 在线求解 | solve_admm_mpc.m | 用ADMM实现实时MPC求解,输出控制序列 |
| 结果评估 | evaluate_results.m | 对比燃油消耗、SOC轨迹、求解时间等指标 |
数据流是这样走的:参数初始化模块定义一切常量和模型系数→工况生成模块算好每个时刻的需求功率序列→参考求解模块把完整问题用CVX跑一遍,拿到基准解→在线求解模块按MPC滚动时域逻辑,在每一采样步调用ADMM求解当前窗口的优化问题→结果评估模块汇总两条链路的输出,做对比分析。
5.2 关键代码段解析
第一段是车辆与电池的初始化和模型系数:
% 整车参数 m = 1700; % 整车质量 kg g = 9.8; f = 0.012; % 滚动阻力系数 rho_air = 1.225; % 空气密度 kg/m^3 Cd = 0.3; % 风阻系数 Af = 2.3; % 迎风面积 m^2 delta_r = 1.05; % 旋转质量换算系数 % 电池参数 E_bat = 12; % 可用能量 kWh SOC_min = 0.25; SOC_max = 0.9; SOC_0 = 0.8; P_bat_max = 45; % 最大充放电功率 kW % MPC参数 Np = 20; % 预测时域步数 Ts = 1; % 采样周期 s % ADMM参数 rho_admm = 20; % 惩罚参数 max_iter = 80; % 最大迭代次数 tol_primal = 1e-3; tol_dual = 1e-3;第二段是ADMM在线求解器的主循环,这也是整个代码的核心。以单个MPC采样步为例,内部要做的事情包括初值更新、预测窗口生成、ADMM迭代:
function [P_ice_opt, P_bat_opt] = solve_admm_mpc(SOC_now, P_req_pred, params) Np = params.Np; rho = params.rho_admm; % 初始化 P_bat = zeros(Np,1); P_ice = P_req_pred; lambda = zeros(Np,1); % 预计算系数(动力学递推的矩阵分解) [A_soc, B_soc, M_inv] = precompute_matrices(params); for iter = 1:params.max_iter % x更新:SOC轨迹在给定P_bat下的最优状态 SOC = M_inv * (A_soc * SOC_now + B_soc * P_bat); % z更新:投影到功率平衡与限值可行域 z = project_feasible(P_req_pred, P_ice, P_bat, params); % lambda更新:对偶上升 lambda = lambda + rho * (z - P_bat); % 收敛检查 r_prim = norm(z - P_bat); r_dual = rho * norm(z - P_bat_prev); if r_prim < params.tol_primal && r_dual < params.tol_dual break; end P_bat_prev = P_bat; end end这里project_feasible函数做的是把控制量投影到可行域内:保证P_ice + P_bat = P_req,同时P_ice和P_bat都在各自的上下界内。由于这些约束都是简单的区间和线性等式,投影计算有解析形式,不需要迭代求解,这也是ADMM高效的原因。
第三段是MPC滚动控制的主循环:
for k = 1:length(P_req)-Np % 取预测窗口内的需求功率 P_req_window = P_req(k:k+Np-1); % 调用ADMM求解当前窗口的控制序列 [P_ice_seq, P_bat_seq] = solve_admm_mpc(SOC_hist(k), P_req_window, params); % 只执行第一个控制量 P_ice_opt(k) = P_ice_seq(1); P_bat_opt(k) = P_bat_seq(1); % 更新SOC SOC_hist(k+1) = SOC_hist(k) - Ts/E_bat * P_bat_seq(1); end这个循环体现了MPC的“滚动”本质:每个采样步都重新求解一次优化问题,但只实施第一步指令。
5.3 参考求解与基准校验
用CVX求参考解的方法,就是把上面ADMM内部复杂的迭代逻辑整个替换成一次完整的凸优化求解。前面3.3节给的代码就是参考求解的核心。我通常是先跑CVX,把结果存成mat文件,再用ADMM的结果和它做逐时刻对比。对比指标有三个:燃油消耗总量、电池SOC轨迹、以及求解耗时的平均值和最大值。
我还做了一个自动化脚本,随机生成多条工况,分别用两种方法求解,然后把误差统计打印出来。这个脚本帮我发现了一个很有意思的现象:ADMM在平稳工况下的解和CVX几乎完全一致,但在剧烈加减速的片段会有轻度偏差。原因是工况剧烈变化时,ADMM的初始点离最优解比较远,有限迭代次数内没能完全收敛。解决办法是在每个MPC采样步,用上一个窗口的最优解作为当前窗口迭代的初始点,也就是“热启动”。这个技巧显著减少了迭代次数要求,80次迭代的精度直接提升了一个数量级。
6. 典型工况结果与调参经验
6.1 工况测试与控制效果
我用的验证工况是WLTC(全球轻型车测试循环)和UDDS(城市工况)两种,也加了一段自定义的激烈驾驶循环。先说结论:在WLTC上,MPC(无论CVX还是ADMM)相比传统的CD-CS(电量消耗-电量维持)规则策略,等效燃油消耗降低了约8%到10%;相比在线瞬时优化策略(ECMS即时调节等效因子),还能再省1%到2%。
这个结果符合预期:规则策略是固定阈值切换,完全忽略未来驾驶需求;ECMS虽然也是优化,但只看当前时刻,没有前瞻性;MPC因为有预测窗口,能在预见坡度、预见停车减速的情况下提前调整发动机工作点,自然更省油。
SOC轨迹的表现也很有代表性。MPC会让SOC在预测窗口内平缓下降,接近约束边界时提前回收电能,而不是像CD-CS那样简单粗暴地在SOC掉到阈值后强制切到CS模式。从驾驶性角度看,MPC的发动机启停、模式切换次数更少,整车NVH体验会更好。
6.2 参数敏感性分析
先看预测时域(N_p)的影响。我把(N_p)从10步扫到40步,发现燃油消耗随(N_p)增大单调下降,但下降速度会饱和:从10步到20步能省大约2%的油,从20步到40步只再省0.5%。而求解时间几乎和(N_p)成正比增长。综合权衡下来,20步到30步是比较甜点的区间,再长就得不偿失了。
ADMM惩罚参数(\rho)的影响前面提到过。参数太小,收敛慢;参数太大,对偶变量振荡。我用的是自适应方案:每次迭代后比较原始残差和对偶残差,如果前者大于后者的10倍就调大(\rho),反之调小(\rho)。实测稳健性比固定(\rho)好很多,对不同工况的适应性也更强。
迭代次数上限我控制在60到100之间。80次迭代在绝大多数工况下能达到高精度,偶尔遇到剧烈变化的工况窗口,靠热启动也能在40次以内收敛到可接受精度。如果追求极致实时性,可以把迭代次数压到30次配合早停准则,代价是精度略有下降,但在线控制的闭环误差会被下一采样步的部分校正抵消,总体表现并不会差太多。
7. 踩坑实录:常见问题与排查方法
7.1 CVX安装与许可证问题
CVX安装本身不复杂,从官网下载对应平台的压缩包,解压后在Matlab里cvx_setup即可。但许可证这个问题很经典:CVX需要申请一个免费的学术许可证文件cvx_license.dat,申请时填写的邮箱收到许可后,需要把文件放到CVX的安装目录下,然后重新运行cvx_setup。很多人搞不定是因为下载的是破解版或者旧版本,文件损坏导致许可证检查不通过。官方学术许可完全免费,没必要折腾。
常见的报错是Error: “cvx/begin” is not found,先检查路径是否设置正确,addpath(genpath('cvx目录'))之后运行cvx_setup,看到提示“CVX is ready”才算成功。
7.2 cvx变量与double混用的坑
CVX变量在求解之前是一个特殊的cvx对象,不能直接和普通Matlab数组做加减乘除,除非操作是CVX支持的“表达式构建”操作。我踩过最典型的一个坑是:在CVX约束里写if SOC(k) > SOC_max这种判断语句,CVX直接报错。正确做法是用pos(SOC - SOC_max)或者max(SOC, SOC_max)这种凸算子来构建软约束。
另一个常见问题是求解结束后,cvx_optval已经更新了,但value(variable)提取结果时没有加括号,或者提取的是旧值。解决方法是:求解完成后立即调用SOC_sol = value(SOC);赋值给普通变量,再继续做其他处理,避免拿着cvx对象去做绘图或传参。
7.3 求解器的数值警告
CVX默认用SeDuMi或SDPT3,求解QP时偶尔会蹦出“Numerical problems”或者“Status: Failed”的警告。这种现象多半是问题本身的数值尺度差异太大导致的,比如发动机功率的单位是kW,SOC无量纲且范围在0到1之间,两者在矩阵里的量级差了三个数量级。
我的处理办法是做完所有变量的归一化:功率统一除以一个基准功率(比如100kW),SOC以0.5为中心除以0.5做归一化。这样所有优化变量都落在差不多的数量级,数值问题基本消失。如果还不行,就换Gurobi或Mosek作为后端求解器,这类商业求解器的预处理和数值稳定性确实比免费求解器好很多。
7.4 MPC无解与递归可行性
MPC在工程实现中最大的坑之一就是不可行问题。比如SOC已经逼近下限,而预测窗口内还要求持续大功率输出,此时原始约束互相冲突,优化问题无解。如果没有处理机制,MPC控制器会直接崩溃。
我的方案在3.2节提到过:给SOC约束加松弛变量,并把松弛惩罚加入目标函数。这样优化器永远不会“无解”,只会尽量把SOC的越界程度降到最低。实际效果是在SOC比较极端的时候,控制器会自动提前减少电池出力、加大发动机出力,相当于“软避让”,整车依然能稳定运行。
7.5 ADMM收敛不稳定的调试技巧
ADMM迭代发散或者振荡,先别急着调(\rho),先检查子问题求解是否正确。一个简单的验证方法就是把ADMM的迭代残差画出来,如果原始残差一直下降但对偶残差上升,通常是(\rho)偏小;如果原始残差振荡停不下来,通常是(\rho)偏大或者是热启动初始点差异过大。
另一个技巧是用“诊断模式”在代码里加开关,每10次迭代输出残差和目标函数值,对比CVX的参考值,判断收敛趋势是否正常。ADMM算法理论上有全局收敛保证,实际不收敛几乎都是实现细节的问题,比如投影算子写错、矩阵分解没有随参数更新、或者对偶变量更新方向搞反了。
写在最后的小心得
这套“凸优化+MPC+ADMM”的组合,解决的核心矛盾就是精度和实时性不可兼得的问题。CVX负责把数学模型快速搭起来,帮你在算法层面验证可行性;ADMM负责在硬件上跑出可用的求解速度。我个人最大的体会是:调模型比调算法重要,把问题凸化成形之后,后面的一切都顺理成章了。
如果你只做离线仿真研究,CVX+MPC已经够用;如果要往实车控制器或者嵌入式方向走,那ADMM或者同类的一阶方法是必由之路。我在测试中还发现,ADMM的迭代次数在热启动情况下可以大幅压缩,配合C代码生成,完全可以跑在车载ECU级别的算力上。
最后分享一个操作细节:不管用CVX还是ADMM,先把模型、约束、目标函数的量纲统一好,写代码之前用纸笔把数学形式推一遍,比在Matlab里反复试错有效得多。这个项目后续如果能加上坡度预测、交通信号灯信息,MPC的前瞻性还能再上一个台阶,这也是我下一步准备做的方向。