ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

动态规划如何精准计算混动油耗?原理与Python实现全解析

动态规划如何精准计算混动油耗?原理与Python实现全解析 做混动项目的人应该都有过这种经历领导丢来一条工况曲线说“把这台车的最低油耗算出来”结果你翻出Excel套了个简单的比例放大法被仿真工程师用“SOC不平衡”和“发动机工作点不合理”几个词堵得哑口无言。后期我把动态规划算法完整地写成一个离线油耗计算程序后才真正理解为什么混动油耗问题必须用这个工具来解。这套程序做的事情其实很朴素输入一段车速工况、车辆参数和部件效率数据输出最优SOC轨迹、最优发动机/电机扭矩分配、以及对应的百公里油耗。它用动态规划算法把整个工况看成一段连续的最优控制问题在每一个时刻都回答“发动机出多少力、电池出多少力最合适”。文章不聊花哨理论只讲程序的结构、状态怎么离散、递推怎么落地、以及我在调程序时踩过的那些坑。1. 混动油耗计算为什么必须用动态规划1.1 规则策略和瞬时优化策略的死穴混动能量管理最直接的做法是规则表SOC高了就纯电SOC低了就强制发动机充电。这种策略逻辑清晰、实车部署简单但它的本质是“只看当前状态不看未来路况”。市区低速起步阶段明明电能应该留着应付后面爬坡规则表却可能因为SOC高就早早把电放光高速巡航阶段发动机效率已经很高了规则表却又可能自作主张地充电。算出来的油耗自然不是最优。另一类常见做法是等效燃油消耗最小策略ECMS它把电能消耗折算成虚拟燃油消耗然后每一秒都找“发动机油耗电池等效油耗”最小的点。这种方法比规则表聪明但它是个瞬时优化没有前瞻能力。用ECMS跑一条“先拥堵、后通畅”的工况它会因为不知道后面能充电而不敢放电最后的油耗离全局最优还有明显差距。动态规划算法不一样。它把整个工况从终点往前递推让每个时刻的决策都包含“这一步省下的油”和“剩下路程的最低成本”两部分。换句话说第一个时刻的发动机功率选择不是只看当前而是把后两个小时的路程全部考虑进去。所以动态规划算出来的油耗才是真正意义上的“这条路况下理论上最低的油耗值”。1.2 动态规划在这个程序里到底算的是什么混合动力汽车的油耗问题本质上是一个带状态约束的序贯决策问题。时间被离散成N个步长每个步长内的控制量是发动机扭矩或功率分配系数状态量是电池SOC。目标函数是整个工况的燃油消耗总和最小化。这个结构天然适合动态规划求解。我用动态规划算法写油耗计算程序不是为了上线实车而是为了做“全局最优基准”。有了这个基准后面无论是调规则、标ECMS系数、还是训练神经网络都知道天花板在哪。程序返回的最低油耗不只是一个数字它本身就是一套最优SOC轨迹和发动机工作点分布这些数据对实车策略调试特别有价值。2. 状态空间与效率表程序运行前先决定“用什么算”2.1 从车速工况到需求功率的换算细节油耗计算程序的第一步不是递推而是把车速时间序列换算成整车的需求功率序列。这一步省事不得后面所有成本计算都建立在它的准确性上。我按行驶阻力公式算轮边需求功率[ P_{wh} \left(M \cdot g \cdot f_r \cos\theta \frac{1}{2}\rho C_d A v^2 M \delta a\right)\cdot v ]这里v是车速a是加速度fr是滚动阻力系数Cd是风阻系数A是迎风面积δ是旋转质量换算系数。程序里还需要考虑坡度阻力WLTC和NEDC工况通常坡度为零但如果是自定义坡度工况就要补上M * g * sinθ * v这一项。计算时加速度a不能用相邻两个点的简单差分最好先用滤波把车速平滑一下否则高频噪声会被加速度项放大需求功率曲线会出现很多毛刺影响后面的DP寻优结果。得到轮边功率后再加上传动损耗[ P_{req} \frac{P_{wh}}{\eta_{gb}} P_{acc} ]ηgb是变速器和主减速器的综合效率我一般取0.92~0.95。Pacc是空调、转向泵等附件功率实车抓下来的数据里这部分往往不可忽略冬天开暖风时附件功率能到2kW以上DP算出来的最优策略会明显偏向发动机工作因为要保证电量不崩。2.2 SOC状态离散化和电池等效模型状态量我只保留SOC用一阶等效电路模型描述电池行为。程序里电池不是一个固定的电压源而是开路电压与内阻的组合[ P_{bat} V_{oc} I - I^2 R_{bat} ]这里Pbat的正方向我定义为电池放电功率负方向为充电功率。根据需求功率和发动机功率的差值可以得到Pbat然后反解电流I。欧姆内阻Rbat和开路电压Voc都随SOC变化模型里直接以查表形式给出。SOC的更新公式是[ SOC(k1) SOC(k) - \frac{I_{bat} \cdot dt}{Q_{bat}} ]注意dt的单位秒和小时不能搞混。电池容量Qbat我习惯用安秒表示例如40Ah写成144000As这样SOC变化量直接是无量纲的百分比。SOC的离散范围我通常限制在0.3到0.8之间超出这个范围会严重加速电池衰减程序中直接把越界的候选解剔除。2.3 发动机和电机的万有特性查表油耗计算的“油”来自发动机所以发动机的燃油消耗率表是整个程序的灵魂。我用的是BSFCBrake Specific Fuel Consumption查表横轴是发动机转速纵轴是扭矩表里存的是g/kWh的燃油消耗率。某一工作点的小时油耗流量用下面公式算[ \dot{m}f \frac{P{eng} \cdot bsfc(n_e, T_e)}{3600 \times 1000} ]单位改为g/s。程序里我在初始化时把这个表预处理成和状态网格对齐的二维矩阵避免递推循环里反复查表拖慢速度。电机效率表同理横轴是电机转速纵轴是扭矩表里存的是充放电效率。程序里同时考虑电机控制器损耗但为了简化最初版本我把控制器效率和电机效率合并成一张二维表差不了太多。需要提一点驱动时电机效率一般在0.90~0.94发电时效率会略低一点大约0.85~0.9两套效率分布不能混用否则SOC会被虚高估计。3. 反向递推Bellman方程与SOC终值惩罚的核心实现3.1 状态转移成本如何写进递推式动态规划的核心是Bellman方程程序里我用如下形式[ J_k(SOC_i) \min_{u \in U_k}\left[ \dot{m}f(k, SOC_i, u)\cdot dt J{k1}(SOC_{i1}) \right] ]一段工况一共有N个时间步dp程序从最后一个时刻开始逐步往前递推。每个时刻枚举所有SOC状态再枚举所有可行的控制量u计算这一步的燃油消耗和下一时刻SOC用插值从J_{k1}里取续值成本。这一步计算的燃油流量是瞬时油耗单位是g/s乘上时间步长dt后变成这一秒的油耗克数。控制量u我一般是发动机扭矩。给定需求功率和发动机转速后发动机扭矩确定了电机扭矩也就确定了需求由电机补足。要枚举的控制量集合在每个时间步都不一样工程师可以根据当前车速确定发动机转速范围再限制发动机扭矩上下限候选控制量从几百个到上千个都有可能。3.2 SOC终值约束的两种常用处理如果在程序里直接让SOC任意飘动态规划算法很快会把SOC放到下限因为用电比烧油“便宜”最终算出一个SOC大幅下降的假最优解。所以必须对终值SOC做约束。我的第一版程序用的是硬约束最后一步只允许SOC落在目标值附近一个窄带比如0.5±0.02其他位置的J值设为极大。这个方法思想干净但SOC网格如果不够密很可能在最后几步找不到可行解程序直接报错。后来我换成了二次罚函数[ J_N(SOC_i) \alpha \cdot (SOC_i - SOC_{target})^2 ]α取一个很大的值比如1e5。因为成本量纲是克罚函数量纲是SOC偏差平方α要调节到“终点每偏0.01SOC就相当于多耗几十毫升油”的尺度。我实际计算后感觉这个软约束比硬约束稳得多只要α足够大终点SOC实际上会被拉回到目标附近。灵敏度分析显示α从1e4改成1e5对前一段SOC轨迹影响不大但对最末端SOC回拉行为有影响工程上取1e5~1e6比较合适。3.3 一个能直接跑的Python计算框架代码我用Python配合NumPy实现一个最小框架方便理解import numpy as np # 假设已计算P_req[0..N-1], 转速n_eng[0..N-1] N len(P_req) SOC_grid np.linspace(0.3, 0.8, 121) N_soc len(SOC_grid) dt 1.0 # 秒 # 成本矩阵行是SOC索引列是时间步 J np.full((N_soc, N), np.inf) U_opt np.full((N_soc, N), np.nan) # 终端成本软惩罚 SOC_target 0.5 alpha 1e5 for i in range(N_soc): J[i, -1] alpha * (SOC_grid[i] - SOC_target) ** 2 # 反向递推 for k in range(N - 2, -1, -1): for i, soc in enumerate(SOC_grid): best_cost np.inf best_u None for tq in candidate_torque_eng(k): # 候选发动机扭矩 # 计算当前瞬时油耗与下一时刻SOC m_dot, soc_next step_cost(k, soc, tq, P_req[k], n_eng[k]) if soc_next SOC_grid[0] or soc_next SOC_grid[-1]: continue # 线性插值取未来成本 j_next np.interp(soc_next, SOC_grid, J[:, k 1]) total_cost m_dot * dt j_next if total_cost best_cost: best_cost total_cost best_u tq if best_u is not None: J[i, k] best_cost U_opt[i, k] best_ustep_cost函数里完成电池功率分配、电机效率查表和SOC更新。正向仿真时从初始SOC出发每个时刻根据U_opt找到最接近的SOC索引取出最优发动机扭矩然后更新SOC。这样一条完整的最优SOC轨迹和最优控制序列就出来了。4. 油耗计算程序里最容易被忽略的几个坑4.1 网格粒度不是越细越好我刚开始做动态规划油耗计算时为了追求“所谓最优解”把SOC网格分到301个点控制量候选扭矩每隔1Nm枚举一次。结果是程序跑了一整夜算出来的结果和101点SOC网格、5Nm扭矩间隔相比油耗只差了0.01L/100km。这个精度差异完全可以忽略但计算时间差了近20倍。网格划分要重点关注“控制量梯度”而不是“SOC梯度”。SOC网格101个点通常足够因为电池SOC只是一个累积变量插值误差对油耗的影响不明显。发动机扭矩候选步长反而更敏感尤其是发动机在高效区附近扭矩差5Nm就可能导致油耗相差3~4克。我的经验是第一遍用粗网格跑通流程SOC取81点、扭矩步长10Nm确认无bug后再把扭矩步长加密到2~3Nm跑正式结果。4.2 无效状态、插值越界与数值发散递推循环里最容易出问题的地方是soc_next超出SOC_grid范围。你辛辛苦苦枚举了一个控制量算出来的soc_next如果越界用np.interp插值时NumPy会直接返回边界值这个行为会把边界值当成“免费到达点”最终导致SOC在边界附近堆积。程序里必须在插值前主动过滤越界状态或者对越界的soc_next赋一个很大的惩罚值。另一个我踩过的坑是电池模型在大电流下反解电流时出现开根号负数。Pbat稍大于Voc²/(4R)时等效电路模型本身就没有实数解这代表电池在这个瞬间已经超出了物理极限。程序里要捕获这种异常把对应的候选控制剔除。一开始我没加这个保护结果某一组数据跑出来SOC轨迹出现断崖式变化排查了很久才发现是电池模型数值发散。4.3 油耗结论必须做SOC修正否则没法对比动态规划算完的油耗结果不能直接拿去和规则策略对比。因为即使有终值惩罚SOC轨迹往往还是会轻微偏离目标点。这个偏离哪怕是0.02SOC对百公里油耗的影响都不容忽视。我处理的思路是正向仿真结束后记录SOC终值与目标值的差然后根据电池能量和燃油低位热值做一个等效燃油修正[ \Delta fuel \frac{(SOC_{end}-SOC_{target}) \cdot E_{bat_usable}}{\rho_{fuel} \cdot LHV_{fuel}} ]E_bat_usable是SOC区间对应的电池可用能量ρfuel是燃油密度LHV是汽油热值。如果SOC终值高于目标值说明其实“偷偷存了电”实际油耗要往上调如果低于目标值说明欠了电池的账油耗要往下调。这一步做不干净你后面的算法对比结论就会被质疑。高频工况模拟中这个修正量大约0.1~0.3L/100km完全不修正的话结论方向都可能反转。5. 典型工况算例与离线结果的落地用法5.1 一个WLTC算例优化结果长什么样我用一套典型B级混动参数跑WLTC工况。整车质量1650kg发动机最大功率110kW电机最大功率60kW电池可用能量约11kWhSOC范围限制在0.3~0.8目标SOC0.5。跑完后的最优SOC轨迹有一个很明显的特点低速段倾向放电、高速段倾向充电、最后末端主动充电回到0.5附近。和基础规则策略对比规则策略百公里油耗5.8L动态规划程序算出来的平衡油耗是5.1L节油率约12%。拉开差距的重要原因不是某个瞬间的油耗变小而是动态规划会把发动机尽量压在BSFC低于240g/kWh的高效区同时SOC轨迹像一条“蓄水池”一样配合路况起伏。规则策略做不到这一点因为它没有全局视角。5.2 从离线最优到在线控制DP程序还能怎么用很多人以为动态规划算完一次就结束了其实这个程序更大的价值是作为“数据发生器”。我会把不同工况、不同初始SOC下的DP最优解收集起来提取出“等效油耗因子曲线”或者“SOC惩罚系数序列”然后把这些离线知识压缩成在线策略。比如把DP结果里的SOC协态轨迹做回归就能得到一条“该工况下每kWh电能折算成多少克燃油”的动态代价曲线。把这个曲线交给实时ECMS控制器在线油耗能明显逼近DP的离线最优解。还有一版我做过的方法是拿DP的最优发动机工作点当训练样本给一个轻量级神经网络拟合控制率实车验证效果也不错。所以我建议你把这个程序定位成“离线标定工具”而不是直接上车的控制器。它算出来的最低油耗值是天花板它解出来的SOC轨迹是手势它给出来的工作点分布是标定表。有了这套程序后续所有在线策略的调优都有了一个可信的参照系。动态规划算法在混动油耗计算程序里的价值不是一次性的“终局答案”而是一条能不断迭代复用的生产工具链。
返回列表