
做插电式混合动力车辆PHEV能源管理研究绕不过去的一道坎就是“求解”。发动机、电机、电池三套动力源凑在一起功率分配问题天然带开关、带强非线性效率曲线你要是直接套用混合整数规划求解器MPC每滚动一步都可能要算几秒甚至几十秒一个标准工况跑完天都黑了。我去年做课题时正好被这个问题卡了一个多月最后是把交替方向乘子法ADMM和CVX工具箱组合到一起用凸优化算法把模型预测控制MPC的在线优化问题完整跑通才算是把这套东西稳定撸下来。这篇文章就围绕这个套路展开为什么PHEV能源管理适合凸优化MPCADMM在里面解决什么痛点Matlab代码每一块怎么写、参数怎么调以及那些你在论文里根本看不到的坑。想复现这套内容你最好有一点Matlab基础装过或者准备装CVX如果完全没摸过CVX也没关系第五章专门讲环境问题和排查手段。本文面向正在做PHEV/HEV能量管理、动力总成控制方向的研究生也适合准备把MPC往工程上落地的工程师。1. 为什么是“凸优化MPCADMM”PHEV能量管理的选型逻辑1.1 PHEV能源管理的本质一个带离散变量的在线优化问题PHEV的能源管理说白了就是在每个时刻决定发动机出多少力、电机出多少力、电池充多少电。并联构型下驾驶员需求功率经过传动系后被发动机和电机的合并输出满足电池则通过电机吸收或释放功率来调节SOC。这里的核心矛盾在于发动机的燃油消耗率是功率的非线性函数存在最优工作区间电池的SOC又不能掉太多或充太满再加上发动机启停、挡位这些离散变量整个问题天然是一类“混合整数非线性规划”。这种问题最麻烦的地方在于离散变量。在线做MPC时如果预测时域是Np步发动机启停和挡位选择就会产生2的幂次组合。Np10还好说Np20时光组合数就上百万任何一个实时求解器都不可能直接暴力搜索。所以真正的课题不是“能不能找到最优”而是“怎么在几十毫秒内找到一个足够好的解”。凸优化的价值就在这里通过合理的松弛和建模把离散变量变成连续变量把非凸的能耗Map凸化得到的问题就能在多项式时间内求解而且解是全局最优的。1.2 MPC的优势把“全局最优”降维成“看得见的未来”很多刚入门的同学会问一个很自然的问题动态规划DP不是能求全局最优吗为什么还要用MPC答案是DP和庞特里亚金极小值原理PMP这类离线方法需要完整已知的工况序列在真实道路上你不可能事先知道驾驶员未来半个小时怎么踩踏板。MPC的思路是“走一步看一步”在当前时刻用当前SOC和未来短时间内的车速预测去解一个有限时域优化问题然后只执行第一步下一时刻再重新来。这种滚动优化的方式天然带闭环反馈可以抵消模型误差和预测误差。而要让MPC在线跑得动核心求解器必须足够快。如果用内点法直接解一个大规模二次规划Np20、变量上百个时单次求解也要几十毫秒到几百毫秒对于1秒采样周期的能量管理也许勉强能用但如果你想把采样时间缩到0.1秒就必须换一种更高效的分解思路。ADMM就是在这种需求下进入视野的。1.3 ADMM的价值把整车大问题拆成可以并行的小问题ADMM交替方向乘子法是把一个大的优化问题拆成多个小问题交替求解的框架。它不像内点法那样直接求解整个KKT系统而是通过引入辅助变量把原问题的目标函数和约束拆开然后交替更新原变量、辅助变量和对偶变量。用一句大白话说就是“两个包工队接力干活一个负责把发动机油耗这块整理好一个负责把功率平衡和SOC动态安排妥当中间的罚款单乘子u逼着两边最后对齐”。在PHEV-MPC场景里整车的优化问题在时间维度上是串行的SOC从第1步演化到第Np步但在变量维度上是有结构的发动机、电机、电池功率相互耦合。ADMM正好可以把“带时间演化的目标函数”和“每时刻的功率平衡约束”拆开各自变成规模很小的凸子问题用CVX快速求解再通过十几到几十次迭代把两边解对齐。这种结构同样很适合将来做硬件并行化每个子问题可以放到不同核上跑。不过需要强调一点ADMM本身不算“凸优化算法”它是个分解框架CVX是建模工具真正做底层求解的是SDPT3、SeDuMi、MOSEK、Gurobi这类求解器。我们平时说“基于凸优化求解PHEV问题”实际上是指“把问题建模成凸问题然后用凸求解器去解”ADMM则负责把大问题切成小块让每个小凸问题都能被CVX稳稳拿下。2. 数学建模与凸化处理从混合整数问题到凸优化的关键一步2.1 纵向动力学与功率需求先算清“车需要多少能量”所有能量管理算法都建立在功率需求之上。给定车辆速度v和加速度a驱动功率需求可以写成P_req v·(m·g·f 0.5·ρ·Cd·A·v² m·a) / η_drive其中m是整车质量g是重力加速度f是滚动阻力系数ρ是空气密度Cd是风阻系数A是迎风面积η_drive是传动系效率。这个式子本身是v和a的函数在MPC里速度序列作为外部输入已知所以P_req可以直接算出来。带制动时功率需求为负理论上可以把一部分负功率分配给电机做能量回收但要受电池最大充电功率限制。我做这套代码时用的一组参数如下你可以直接替换成自己车型的数据参数符号数值整车质量m1500 kg迎风面积A2.2 m²风阻系数Cd0.3滚动阻力系数f0.012传动效率η_drive0.92轮胎半径r0.3 m电池容量Q40 Ah电池标称电压V320 V发动机最大功率P_eng_max80 kW电机最大功率P_mot_max60 kW一个很关键的经验是所有功率量纲统一到kW。很多代码跑出来数值膨胀或者ADMM残差降不下去十有八九是量纲混乱——一会儿用W一会儿用kW导致惩罚项和约束量级对不上。2.2 电池SOC动态与约束近似、凸化、不丢物理意义电池SOC的离散动态可以写成SOC(k1) SOC(k) - P_batt(k)·Δt / (V·Q·3600)这里P_batt是电池输出功率正值表示放电。注意这个等式本身是线性的所以SOC动态约束天生是凸的SOC范围限制也是凸的盒子约束。真正需要小心的是P_batt与电机功率之间的关系电机会有损耗P_batt P_mot / η_mot或P_batt P_mot·η_mot取决于电机处于电动还是发电状态。问题是η_mot本身是P_mot的非线性函数直接建模会让问题非凸。工程上最常见的处理是忽略电机效率随功率的剧烈变化用一个恒定效率近似或者把电池功率建模成电机功率的凸二次函数。如果你要严格一点可以在电机效率Map上取几个典型工况点做分段线性插值在CVX里用max形式表达仍然保留凸性。实际仿真告诉我对于MPC的滚动优化而言电机效率的近似误差会被反馈校正掉一部分不必追求过高的建模精度。2.3 发动机与电机效率模型的凸逼近耗油MAP怎么变成凸函数发动机瞬时油耗mf通常是转速和转矩的二维MAP直接放进优化问题里是个非凸双线性项。常用手段是做一维化处理假设发动机转速由当前挡位和车速决定那么在任何给定时刻mf只是发动机功率P_eng的一维函数。然后在可行功率区间上把它拟合成一个开口向上的凸二次函数mf(P_eng) a·P_eng² b·P_eng c这个拟合精度会直接影响油耗优化效果。我在自己项目里试过几种方案二次拟合最简单但低负荷区误差偏大分段线性插值在CVX里可以用max(a_i·P_eng b_i)的形式实现精度比单一二次好但变量数量变多PWA分段仿射建模则适合配合MOSEK这类求解器。论文里最常见的还是二次拟合加SOC惩罚项因为结构干净、调参容易。电机侧的凸化思路类似。电机损耗可以建模为P_mot的二次函数或者直接忽略损耗让P_battP_mot。对于概念验证和算法研究后者完全够用如果你做的是实车控制器再把损耗项加上也不迟。2.4 目标函数设计燃油经济性怎么量化MPC每一拍要最小化的目标函数我建议写成下面这种形式J Σ_k [ a·P_eng(k)² b·P_eng(k) c ] λ_term·(SOC(Np) - SOC_ref)²第一项是预测时域内的累计燃油消耗第二项是终端SOC惩罚用于保证MPC结束时电池电量不会偏离参考值太远。这里没有把电池电能损耗折算进目标而是用SOC终端惩罚来间接控制好处是不用纠结电耗的“油价折算系数”坏处是SOC参考轨迹给不好时会出现电量持守偏差。一个常见坑是很多新手把SOC参考设成固定值比如0.3但从混动逻辑上讲MPC更希望SOC在一个区间内“自由漂移”只在终端附近拉回来。所以我习惯把SOC_ref设成一条从SOC(0)指向目标电量的平滑曲线预测时域越长这条曲线越平缓能量管理策略也就越接近全局优化。剩下的凸性检查很简单二次目标、线性动力、盒子约束整个问题就是一个凸二次规划CVX可以直接声明cvx_begin然后扔给它求解。3. MPC滚动框架与ADMM-CVX求解器实现3.1 预测时域与参考SOC轨迹怎么给MPC“未来视野”MPC的第一步是确定采样时间和预测时域。PHEV能量管理通常采样时间Δt取0.5s~2s预测时域Np取5~20步。注意这里不是越大越好Np越大问题维度越高即使有ADMM分解迭代次数也会增加Np太小又体现不出“前瞻”的优势。我做下来觉得Δt1s、Np10是一个很稳的起点既能覆盖WLTC中典型的加减速片段又不用等太久收敛。车速预测序列可以从标准工况里直接截取未来Np步也可以假设车速保持当前值不变。后者在市区蠕行工况下效果很差因为突然的加速会让MPC“措手不及”。实际做研究时我建议至少把标准工况的完整车速序列存下来按时间窗口滑动提供给MPC。如果将来要和智能网联结合用导航数据或V2X预测车速那这套框架完全不变只需要改预测信号来源。3.2 ADMM拆解功率平衡约束与系统动力学的交替迭代为了让ADMM处理PHEV-MPC需要对原问题进行一种标准拆分。定义x块为包含P_eng、P_mot、SOC的整个预测时域变量序列z块为用来做功率平衡约束的辅助变量。原问题写成min f(x) g(z) subject to x - z 0其中f(x)包含油耗目标和SOC动态约束g(z)包含每一时刻的功率平衡、电机功率约束和电池功率约束。ADMM的三步迭代是x^{k1} argmin_x ( f(x) (ρ/2)·||x - z^k u^k||₂² )z^{k1} argmin_z ( g(z) (ρ/2)·||x^{k1} - z u^k||₂² )u^{k1} u^k x^{k1} - z^{k1}你可以把u理解成“施工监理”每次发现两边没对齐就把这个偏差记入罚款项下一次迭代逼着双方往更接近的方向走。实际代码里x子问题和z子问题都各自包一层CVX求解迭代20~30次后取最终x的前一步解作为当前MPC动作。3.3 CVX建模细节子问题在Matlab里怎么写写CVX代码时你不需要自己推导KKT条件只需把凸问题“翻译”成CVX语法。下面这个片段是MPC单个时刻内ADMM主循环的大致骨架% rho是ADMM惩罚参数Np是预测时域 rho 1.0; x zeros(2*Np Np 1, 1); % 把P_eng、P_mot、SOC拼在一起 z zeros(2*Np, 1); u zeros(2*Np, 1); for iter 1:30 % x子问题油耗目标 SOC动态 cvx_begin quiet variable P_eng(Np) variable P_mot(Np) variable SOC(Np1) minimize( sum(a * P_eng.^2 b * P_eng c) ... (rho/2) * sum_square([P_eng; P_mot] - z u) ) subject to SOC(1) SOC0; SOC(2:Np1) SOC(1:Np) - (P_mot/P_eff_batt) * dt / (V*Q*3600); SOC SOC_min; SOC SOC_max; 0 P_eng P_eng_max; -P_mot_max P_mot P_mot_max; cvx_end % z子问题功率平衡约束 cvx_begin quiet variable W_eng(Np) variable W_mot(Np) minimize( (rho/2) * sum_square([P_eng; P_mot] u - [W_eng; W_mot]) ) subject to W_eng W_mot P_req(1:Np); 0 W_eng P_eng_max; -P_mot_max W_mot P_mot_max; cvx_end % 对偶变量更新 u u [P_eng; P_mot] - [W_eng; W_mot]; z [W_eng; W_mot]; end注意上面代码的关键点x子问题里通过SOC动态把P_mot和SOC耦合起来z子问题里用P_req做功率平衡相当于把原来复杂的全耦合问题切成两块。每一块都很小CVX求解一次只要几十毫秒30步迭代总计也就一两秒在1s采样周期下用这个速度做一个非实时仿真完全足够。如果你要上实时机后面会讲到怎么用热启动把迭代次数压到5~10次。3.4 关键参数标定rho、迭代次数、终止容差ADMM参数标定是最容易让人心态爆炸的环节。先说rho它的量纲本质上是“惩罚强度”。rho太小x和z长期对不齐残差下降像蜗牛rho太大x子问题和z子问题各自被罚得太狠每一步都在剧烈震荡整体收敛也慢。经验做法是先取rho1让问题和约束都在同一量纲下跑然后观察残差曲线如果残差单调下降很慢把rho逐步增大一般按10倍试。如果残差上下振荡不收敛把rho减小或者加松弛因子α1.6~1.8over-relaxation。终止条件一般看两组残差原始残差r norm(x-z)和其对偶残差s rho·norm(z-z_old)。工程上我习惯直接判断norm(x-z)/norm(z) 1e-3就提前退出不等满30次。实测下来热启动条件下10次左右的ADMM迭代已经能给出不错的控制效果。另外别忽略子问题内部CVX的求解精度。CVX默认的求解精度是best但对ADMM来说子问题不需要精确到小数点后八位设置cvx_begin quiet后配合cvx_precision low能明显提速。代价是最终SOC轨迹可能有一点点抖动但MPC的反馈会修正影响非常小。4. Matlab代码实现与仿真结果解读从零搭一套能量管理闭环4.1 环境准备CVX安装、许可证与Matlab版本兼容很多同学倒在这一步不是因为算法难而是CVX装不上或者许可证出问题。CVX是Matlab下的免费凸优化建模工具箱直接去官网下载对应版本压缩包解压后在Matlab当前路径切到CVX目录命令行输入cvx_setup它会自动配置路径并检测可用求解器。如果一切正常你会看到类似“CVX version XXX”和“SDPT3, SeDuMi”等求解器状态提示。许可证是个高发问题。CVX对学术用户是免费的但要到官网注册账号并申请学术许可证。申请时会要求填学校邮箱审批通过后你会收到一个license文件。这里有个很容易踩的坑有人的校园邮箱收不到外域邮件把审批邮件当成垃圾邮件丢了然后一直卡在“no license”报错。我建议申请完之后刷新几次邮箱顺便检查垃圾箱。运行cvx_setup时如果出现许可证相关报错先检查电脑系统时间是否正确再检查hostid每台机器唯一和申请时填写的机器信息是否一致。还有一点部分机构电脑有严格防火墙CVX许可证校验需要访问服务器网络不通也会报-8错误这个在你跑cvx_setup时就能发现。Matlab版本兼容方面CVX新版本2.x对Matlab的兼容性总体不错2020到2023甚至更新版本都能跑。如果你遇到“attempt to execute script cvx_setup as a function”这类诡异报错多半是路径冲突——把工程目录加到Matlab路径最前面或者直接双击CVX目录下的cvx_setup.m运行别用那种野路径下的同名文件。4.2 主程序结构从仿真参数到闭环循环整套代码我推荐分成四个模块结构清晰、排错容易参数初始化车辆参数、电池参数、ADMM参数、CVX精度设置工况生成与功率需求计算读入WLTC或其他工况计算P_req序列MPC闭环主循环按时间步推进每个步内调用ADMM求解器后处理绘图画SOC曲线、功率分配曲线、累计油耗曲线。顶层伪代码非常简单% 读工况 v load_cycle(WLTC.mat); P_req_full calc_Preq(v, m, Cd, A, f); SOC zeros(N, 1); SOC(1) 0.3; for t 1:N P_req P_req_full(t:tNp-1); % 预测窗口 [P_eng_opt, P_mot_opt] admm_mpc(SOC(t), P_req, params); SOC(t1) SOC_update(SOC(t), P_mot_opt(1), dt); % 执行第一步 fuel(t) fuel_rate(P_eng_opt(1)); % 记录油耗 end注意这里每一步都要“只执行第一个控制量”然后重新预测、重新求解。这正是MPC滚动优化的灵魂。如果你一步把Np个控制量全执行了那就不叫MPC叫开环最优控制了。4.3 效率实测ADMM迭代次数与计算时间我把核心求解器写成一个函数admm_mpc.m输入是当前SOC、预测窗口功率需求、参数结构体输出是第一步的P_eng和P_mot。内部就套用第三节的迭代框架。做仿真的时候我会顺手统计“每次MPC调用内部ADMM迭代了多少次才满足终止条件”。以下是一组我在WLTC工况最后一个市区片段跑出来的实测记录Matlab 2023aAMD 5600X单核工况片段Nprho平均迭代次数单次MPC求解时间市区蠕行101.0231.8 ms郊区匀速101.0110.9 ms高速加速101.0272.1 ms市区蠕行热启动101.060.5 ms可以看到热启动的效果非常夸张从平均23次掉到6次这正是工程落地的关键手段。所谓热启动就是保存上一次MPC时刻ADMM收敛时的x、z、u作为本次迭代初值。因为相邻两步MPC的最优解本来就接近有了这个初值迭代收敛速度会大幅提升。4.4 仿真结果解读SOC轨迹、功率分配、油耗对比仿真结束后你会得到三条最核心的曲线SOC轨迹、发动机/电机功率分配、累计油耗。正常工况下SOC应该围绕参考轨迹波动最终落在目标电量附近发动机功率应该避开低效的低负荷区电池在急加速时放电、制动时回收整体功率分配符合直觉。我习惯把这三条线画出来跟“规则策略”对比。规则策略就是最简单的电量维持逻辑功率需求小时纯电功率需求大时发动机介入。用同一WLTC工况跑下来凸优化MPC的等效油耗通常能比规则策略省5%~8%动态规划作为全局最优上界则大约比MPC再低1%~2%。这个差距说明MPC已经拿走了大部分优化红利剩下的差距主要来自预测时域有限和模型简化完全是工程上可接受的。还有一个小提醒别拿单条工况的省油百分比当“硬指标”。PHEV能耗和工况强相关这个5%~8%在纯高速工况下可能缩到1%以内在市区走走停停时可能放大到10%以上。写论文或做汇报时请至少对比3条不同工况并给出SOC终值一致的前提否则对比不公平。5. 常见问题与排查技巧实录避坑手册5.1 CVX环境问题许可证报错、求解器缺失、版本冲突CVX安装阶段的坑前面已经讲过一部分。这里再补充一个高频问题cvx_setup检测不到任何求解器。绝大多数情况下是因为CVX目录不在Matlab搜索路径里或者你运行cvx_setup之前没把当前文件夹切到CVX根目录。还有一种情况是某些杀毒软件把CVX目录下文件隔离了检查一下目录里是否缺sdpt3文件夹。遇到许可证报错licensing error -8先排除网络和系统时间问题确认是CVX官方站点不可达还是hostid不匹配。如果单位有代理或防火墙限制和IT管理员确认一下网络放行策略或者换一个允许外网访问的时段再跑cvx_setup。无论如何走正规学术申请通道是唯一靠谱的路。5.2 ADMM不收敛或残差振荡先别急着调rhoADMM残差不掉坑看起来吓人其实大部分原因不是参数问题而是子问题建模有瑕疵。最常见的有两类第一类是x子问题和z子问题的约束域几乎不相交也就是“两边各说各话”。最典型的例子是x子问题里的SOC动态约束功率范围太窄而z子问题里功率平衡又给得很宽两边交替迭代就是来回弹。解决办法是先不加ADMM单独把x子问题和z子问题用CVX各解一次看分别的可行解在哪确认约束域有交集。第二类是量纲问题。rho取到1e-6或者1e6多半是功率单位混乱导致的。把功率统一到kW、SOC用0~1的小数表示后rho在0.1~10之间就够用了。如果你想要一个检查残差下降曲线的固定流程先把rho固定为1跑50次迭代画出原始残差和对偶残差随迭代次数的变化。残差单调下降但太慢就增大rho残差先下降后反弹就减小rho残差一直振荡检查约束域交集。这一套下来95%的收敛问题都能解决。5.3 CVX报Infeasible约束互相冲突的排查思路CVX报Status: Infeasible是MPC代码里最让人头大的错误。排查时不要盯着CVX语法看先想物理上有没有可能满足所有约束。我排错时按这个顺序查P_req窗口内最大功率是否超过发动机和电机功率之和SOC初始值预测时域内最大可充/放电量是否还能覆盖SOC窗口充电功率限制是否和制动回收需求冲突终端SOC惩罚是否写成硬约束了比如SOC(Np)SOC_ref如果是放宽成软惩罚。一个非常隐蔽的坑是SOC终值写成了硬约束而预测时域太短物理上根本不可能在Np步内从当前SOC拉到目标SOC于是整批约束全部不可行。我的习惯是把终端SOC约束一律改成目标函数里的软惩罚项只在少数明确需要严格保电的场景才用硬约束。5.4 性能优化与扩展热启动、提前终止、算法对比最后聊几个让整个系统真正“能跑”的经验。热启动是最重要的一招效果前面已经用数据说明了。第二个是提前终止如果你对控制精度要求没那么苛刻设定最大迭代次数15次然后取当前解执行效果差别很小但计算时间直接减半。第三个做法是把ADMM的子问题里的CVX求解精度调到low这能让CVX内部迭代次数大幅下降。代码写完之后你还可以做两件很有意思的扩展。一是把挡位变量加进来将挡位-车速-发动机转速做一个有限状态枚举每挡对应一个凸问题ADMM负责协调这是目前学术界比较喜欢的“混合整数模型预测控制”方向。二是把强化学习DQN或PPO作为上层策略跟这套ADMM-MPC做对比——MPC天然就是强化学习的“最优专家”或“安全基线”用来生成训练数据非常好用。我身边有同学就是把这两种方法放在同一篇论文里做的对比实验效果比只报单一算法扎实得多。最后说点个人体会。这套ADMMCVX的代码前前后后改了快两个月最后真正让性能跑起来的反而是那些看起来不起眼的细节量纲统一到kW、SOC差分别用电流而直接用功率、每次MPC把上一次的解带进来做初值。很多资料把ADMM讲得玄乎但落到PHEV能量管理这个场景它就是一把特别趁手的螺丝刀——你不需要理解它所有的数学证明只需要知道它能拆问题、能热启动、能让你在1秒采样时间内完成一次在线优化就够了。如果你正卡在PHEV能量管理或者说MPC求解慢这个坑里花一周时间把这个组合试一遍我觉得比在一台商用求解器上猛堆算力要靠谱得多。