ARTICLE DETAIL

资讯详情

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

电动车续航蒙特卡洛模拟:从单点估算到概率分布决策

电动车续航蒙特卡洛模拟:从单点估算到概率分布决策 1. 为什么要给电动车续航做蒙特卡洛模拟单点估算解决不了“万一”要用一句话说清楚这件事的话电动车续航从来不是一个固定值而我们每个人真正关心的其实是它的分布。我刚接触电动车辆数据分析时有一台测试车标称续航400公里。夏天在城市里来回跑实际能开到390左右到了冬天跑高速开着暖风同一辆车经常只有260出头。这种差异不是个案而是物理规律叠加使用习惯后的必然结果。但有一个问题几乎没人认真回答如果我只想保证“这台车跑一趟300公里的路中途不充电”我的置信度到底是多少是直接出发还是先充电更稳当用确定性公式去算无非是代入一个平均速度、一个温度、一个负载得到一条续航曲线。可是现实中平均速度不会固定在某个点温度也不是恒定的空调什么时候开到最大、行李装多重、路上有没有坡这一切都有波动。单点估算最大的问题就是它把不可能同时出现的极端值混在一起或者反过来把所有不利因素都摊均匀了结果既骗自己也算不出风险。蒙特卡洛方法恰好是给这类问题准备的。核心思路很简单把每个不确定参数当成随机变量按它们各自可能的范围采样然后完整跑一遍车辆能耗模型记录结果。重复几千到几万次之后你会得到一组“续航结果样本”而不是一个数字。对这组样本做百分位统计就可以回答“100次出行里有多少次会出现电量告警”或者“充电时间最长的一次用了多久”。我选 Matlab 来做这件事一是因为它的数组运算和矩阵操作很适合蒙特卡洛这种重复计算场景二是符号计算与绘图工具就在一起模拟完直接出分布图不需要再倒一次数据。2024a 之后的版本跑这种 1 万次循环的模型完全没有压力代码写清楚了后续改参数、加输入维度都方便。你完全可以把它看作一套“概率化续航评估框架”而不仅仅是一个计算脚本。这套模型核心要处理的对象是两个行驶里程对应的能量消耗以及充电时实际补充进去的功率。前者决定“我还能跑多远”后者决定“我要充多久”。这两者的分布合在一起才能回答完整的问题。接下来我把它们的建模方法、仿真代码、结果解读和常见坑逐一展开。下面的章节我尽量写成可以直接照抄的研究笔记而不是教科书式的理论推导。2. 行驶里程的物理基础先把确定性公式搭对再注入随机参数2.1 行驶阻力与百公里电耗我常用的一组基准参数续航仿真的第一步是把车辆行驶过程中的能量消耗用物理公式描述出来。如果这一步偏差太大后面再怎么蒙特卡洛都是白搭。我习惯把行驶工况简化为恒速段叠加因为对于长距离续航评估来说频繁加减速的瞬态过程对总能耗的影响可以通过一个效率系数吸收掉。车辆匀速行驶时需要克服的阻力主要有三项滚动阻力F_roll m × g × Crrm 是整车质量g 取 9.8Crr 是滚动阻力系数一般轿车在 0.0100.015。空气阻力F_drag 0.5 × ρ × Cd × A × v²ρ 是空气密度Cd 是风阻系数A 是迎风面积v 必须换算成米/秒。坡度阻力F_grade m × g × sin(θ)θ 是道路坡度。平路可以忽略山区路线建议加上。驱动电机提供给车轮的功率就是这些阻力之和乘以车速。再加上传动效率 η整车从电池侧消耗的功率大约是P_electric (F_rolling F_drag) / η × v 辅助负载如果要从这个功率换算到“百公里电耗”更直观的写法是E_per_100km (P_electric × 100) / v_avg其中 P_electric 单位是 kWv_avg 单位是 km/h。这样算出来的是 kWh/100km是我们日常看表显电耗最熟悉的单位。我常用的一组车辆参数是整备质量 2100 kg含乘员和行李大约到这个水平风阻系数 0.27迎风面积 2.3 m²滚动阻力系数 0.012驱动系效率 0.88。代入上面公式按平均速度 90 km/h 计算电机牵引能耗大约是 14.5 kWh/100km。加上空调、音响、灯光这类辅助负载之后整车电耗通常在 1620 kWh/100km 之间。注意这个值本身不是固定的它随速度升高而上升尤其是空气阻力与速度的平方成正比跑高速比市区挪动要敏感得多。2.2 温度衰减、辅助负载与可用电量最容易忽略的随机源确定性公式只是骨架。实际仿真里每个输入都应当按“现实中它可能取什么范围”来随机化。我最看重三个随机源。第一个是环境温度。锂电池在低温下内阻增大、可用容量下降零下 20℃ 时可用容量通常只有常温的七到八成反过来超过 30℃ 后热管理又会让电量折损一点。我一般用一个分段插值函数来近似温度对电池容量的影响。第二个是辅助负载。这里最容易被低估。常温下空调压缩机功率在 1.53 kW冬季 PTC 加热器烧起来可以到 47 kW再加上座椅加热、除雾电耗不是小数目。按 100 km/h 跑一个小时能耗多出 24 kWh对于一块 60 kWh 的电池来说就是几个百分点的 SOC。蒙特卡洛里如果不把辅助负载设为随机变量结果往往过于乐观。第三个是电池可用容量的动态变化。电池额定容量是 60 kWh但日常使用中不可能真正把它用到 0保护策略一般允许放电到 5%10% SOC同时电池老化SOH 下降也要考虑。所以仿真中我从来不写“用满 60 kWh”而是把可用能量写成usableCap batteryCap × SOH × tempFactorSOH 我通常设置成 0.9 甚至 0.85 来模拟中后期车辆的真实状态。这个值放到蒙特卡洛里再乘以一个随机波动系数就能覆盖不同批次电池之间的差异。2.3 把驾驶风格变成分布平均速度与路线长度的建模思路驾驶风格的随机化没有标准答案我的做法是按路线场景组织。假设要评估“从城市边缘到异地的高速路程”我就将路程采样为一个连续均匀分布或三角分布比如 120280 km。平均速度也按路段类型采样城市拥堵 2035 km/h郊区 4060 km/h高速 90120 km/h。对于长距离出行我更推荐把路线切段建模。比如第一阶段城区、第二阶段高速、第三阶段山区每段的距离占比和平均速度分别从不同分布里采样最后把各段能耗累加。这样做的好处是避免了把高速和市区混在一个平均速度里导致空气阻力项被严重低估。蒙特卡洛本来就是用来处理复杂组合的没必要为了省事把所有东西压缩成一个标量。随机变量之间还有一个容易忽略的问题相关系数。比如低温天气里开暖风几乎是必然的那么温度低和辅助负载大这两个参数就不应该独立采样否则会出现“很冷但空调没开”的伪样本。我一般通过条件抽样来处理至少把辅助负载设为温度的函数而不是简单粗暴地让二者完全独立。这个细节后面单独展开。3. 充电功率建模恒定功率和正态分布都是危险的简化3.1 真实充电过程几乎不存在“恒定功率”如果你做过充电桩实测会发现充电功率是一条随时间变化的曲线而不是一个固定数字。交流慢充7 kW 左右相对平稳但直流快充的功率曲线非常明显刚插上时可能瞬间冲到 120 kW随着 SOC 上升电池内阻和保护策略会让功率逐步下调到 80% 以后可能跌到 40 kW 甚至更低。很多初学蒙特卡洛的人会把充电功率设为常数比如“桩是 60 kW那就按 60 kW 算充电时间”。这在慢充场景下误差不大但是在快充场景下会把充电时间低估 20%40%。假设一辆车从 10% 充到 80%平均功率如果只有峰值功率的 60%那么按峰值算出来的时间自然严重偏乐观。正确的做法是把充电桩额定功率和车辆接收功率分开看。整车请求功率通常受充电桩上限、电池温度、SOC 阶段三方约束。蒙特卡洛里我习惯分两步根据 SOC 起始区间决定采用哪一段充电曲线再根据该区段的平均充电功率计算补充电量所需时间。这样既保留物理真实性又不会让模型复杂到难以维护。3.2 为什么不能直接对充电功率用 randn 或者正态分布这是我在代码评审里反复强调的问题。有些人图省事直接P mu sigma * randn(...)来生成充电功率样本。这样出现的第一个问题是正态分布样本可能取负值而充电功率在物理上不可能小于 0。第二正态分布没有上界可能生成 300 kW 的充电功率市面上几乎没有乘用车能长时间吃下这个功率。样本一旦生成后续的充电时间计算会跟着产生荒谬的数值。正确做法是使用截断分布。Matlab 自带函数可以直接构造pdNorm makedist(Normal, mu, 60, sigma, 12); pdTrunc truncate(pdNorm, 20, 90); % 物理上下限 P_sample random(pdTrunc, 100000, 1);truncate 会把落在范围外的概率重新归一化到区间内既保留了正态分布的形状又不会产生负值或者超常规的大功率。另一个更贴近工程实际的方案是直接用经验分布。你手里如果有某款车型的实测充电数据用histogram看形状之后选用对数正态分布或威布尔分布拟合再通过fitdist生成样本。这样得到的充电功率分布比任何拍脑袋的正态分布都靠谱。3.3 我用的充电模型按 SOC 区间映射平均功率为了兼顾精度与计算速度我在蒙特卡洛主循环里没有逐秒仿真充电过程而是把充电曲线离散化成几个 SOC 区间每个区间对应一个平均接收功率做成查找表。比如一个 120 kW 的直流快充桩典型近似可以是SOC 区间平均充电功率kW5%30%10030%60%8560%80%6080%95%35这里数值只是示例不同电池的热管理能力差别很大。真正的数据应该从实车充电测试里拿或者至少使用厂家公布的充电曲线做插值。主循环中只要知道当前 SOC 落在哪个区间就取出对应功率和充电桩额定功率取最小值再除以补电所需能量就得到该段的充电时间。如果你希望模型更细还可以加入电池温度对充电功率的影响冬天冷电池低温下充电功率会被限制这是另一种随机源。我会把冬天充电功率的上限单独打折并在truncate的范围设置中体现出来。4. Matlab 仿真实现一套可以直接跑通的蒙特卡洛代码4.1 随机种子与输入参数表先锁住结果再说蒙特卡洛可比拼的就是能复现。如果每次运行得到不同结果你就没法调试对错。所以代码第一行我会写rng(42); % 任何固定整数都可以拿到哪个都一样固定随机种子之后整个项目对同一个输入参数集合永远给出同一组结果。这在调整模型结构时特别重要——你知道结果的差异来自模型改动而不是运气。接着把车辆固定参数当作结构体集中管理不要散落到循环里到处魔改数字。这样后期更换车型、对标不同电池版本只需要改一个地方veh struct(); veh.mass 2100; % kg veh.cd 0.27; veh.area 2.3; % m^2 veh.crr 0.012; veh.rho 1.2; % kg/m^3 veh.eff 0.88; veh.batteryCap 60; % kWh veh.soh 0.9;4.2 主循环拆解一次模拟到底在算什么我建议先用普通的 for 循环把逻辑写明白再去优化性能因为蒙特卡洛的核心是可读性。下面这段代码基本是我最早版本的原型之后只是在细节上做加法numSim 10000; finalSOC zeros(numSim, 1); chargeTimeMin zeros(numSim, 1); distanceKm zeros(numSim, 1); tempFactorFunc (t) interp1([-20 0 10 20 30], ... [0.72 0.86 0.94 1.0 0.96], t, linear, extrap); for i 1:numSim % --- 随机输入 --- distanceKm(i) 120 180 * rand; % 单次出行距离 120~300 km plausibleSpeed [25 55 80 95 110]; % 典型路段车速 avgSpeed plausibleSpeed(randi(5)); % 从典型路段中随机抽一个 temp -5 15 * rand; % 冬季场景 -5~10°C auxLoad 2 3 * rand; % 辅助负载 2~5 kW % --- 能耗模型 --- F_roll veh.mass * 9.8 * veh.crr; F_drag 0.5 * veh.rho * veh.cd * veh.area * (avgSpeed / 3.6)^2; drivePowerKW (F_roll F_drag) * (avgSpeed / 3.6) / 1000 / veh.eff; totalPowerKW drivePowerKW auxLoad; consumedKWh totalPowerKW * distanceKm(i) / avgSpeed; % 总耗电量 % --- 电池可用容量 到达SOC --- usableCap veh.batteryCap * veh.soh * tempFactorFunc(temp); socArrivePct 100 * (usableCap * 0.95 - consumedKWh) / usableCap; % --- 充电过程补电并记录时间否则记录剩余电量 --- if socArrivePct 5 socArrivePct 5; % 电池保护兜底 end startSOC socArrivePct / 100; targetSOC 0.8; if startSOC targetSOC chargeTimeMin(i) 0; else neededKWh (targetSOC - startSOC) * usableCap; if startSOC 0.3 avgChgPow 100; % 快充高功率段 elseif startSOC 0.6 avgChgPow 85; else avgChgPow 60; end chargeTimeMin(i) neededKWh / avgChgPow * 60; end finalSOC(i) socArrivePct; end这个循环的逻辑一句话总结随机生成一次出行条件算一遍能耗看剩余 SOC再按 SOC 区间取充电功率算充电时长。跑完 1 万次之后finalSOC 和 chargeTimeMin 就是两个随机样本集合后续所有统计都围绕它们展开。注意这里的 avgSpeed 是随机离散值不是连续分布的。你也可以改成连续均匀分布但用离散值的好处是能明确代表不同场景不容易出现“平均速度 87.3 km/h”这种十分别扭的样本。如果想更平滑可以在每段速度内再加正态扰动。4.3 性能优化一万次循环只是起步五万次怎么办上面的循环在普通台式机上跑 1 万次速度很快不需要优化。但如果你要跑 5 万次甚至 20 万次有几个优化手段是立竿见影的。第一个是parfor。把 for 换成 parfor同时确保每个迭代之间没有依赖关系代码基本不需要改。我第一次用的时候犯过一个小错循环里给distanceKm(i)写值这种方式在 parfor 中是完全允许的但要确保变量不是成长数组。提前预分配好输出数组就不会报错。第二个是使用“分层采样”替代纯随机采样。蒙特卡洛的随机样本有时会出现局部密集、局部稀疏尤其整体概率分布尾部样本不足时会导致尾部百分位估计不准。可以用lhsnorm或者自己写拉丁超立方采样工具先把参数分布均匀切层再抽样保证尾部也被覆盖到。第三个是减少无谓的重复计算。比如 tempFactorFunc 是对每个温度样本做插值如果百万次循环这个插值开销也不小。可以先把插值表预计算成密集表格循环里直接查最近点。性能提升肉眼可见。5. 结果解读把分布图看懂你才算真正完成仿真5.1 从直方图到百分位平均值没有给到你的信息仿真跑完先画直方图figure; histogram(finalSOC, 30); xlabel(到达SOC (%)); ylabel(频数);如果直方图是右偏的说明多数情况下电量充足但少数恶劣条件下 SOC 被压得很低。这时候你会特别想知道最差的 5% 是什么水平。Matlab 一行就能拿到分位数Q quantile(finalSOC, [0.05 0.25 0.5 0.75 0.95]);假设输出是 [18, 32, 45, 58, 71]含义是在最差的 5% 场景里到达电量只剩 18%一半的场景能达到 45%最好的 5% 场景能有 71% 剩余。显然如果你要保证“不充电也能完成出行”看平均值 45% 是没有意义的你需要看的是 5% 分位数 18%或者更严格地看 1% 分位数。5.2 充电时间的分布覆盖一个区间才能做充电规划充电时间同样不要只看一个数。将 chargeTimeMin 画出来之后大概率是长尾分布——多数情况一小时出头偶尔因为低温快充功率被限制充到 80% 要两三个小时。我通常会同时算两个指标中位充电时间和 90% 分位充电时间。中位数代表“一般体验”90% 分位代表“我该按最坏情况预留的时间”。如果 90% 分位是 2 小时那在沿途充电站规划时就要按 2 小时而不是 1 小时来排行程。再进一步可以按充电桩类型拆分。比如将慢充桩7 kW和快充桩平均 80 kW分别跑一遍对比充电时间分布的重叠程度。这个对比结果可以直接支撑充电站建设评估一个快充站到底要配几个枪才能把排队时间的 90% 分位压到用户可接受的范围。5.3 这套结果的实际用途从车型评估到路线预判蒙特卡洛的最终价值不是输出一堆图形而是进入决策流程。我现在做车型评估时会把不同车辆的参数分别跑同一套随机输入看它们的“5% 分位续航”差多少。这比拿标称续航对比更有说服力因为标称续航是理想条件而 5% 分位续航反映了天气、路况与负载的联合压力。做长途出行规划时我会直接把仿真结果转化为“电量告警概率”统计 finalSOC 低于 20% 的次数占总模拟次数的比例。如果这个比例是 8%就意味着跑这条路一百次大概有八次会陷入低电量的被动局面。再结合沿途充电站间隔距离就能判断是否需要在中途安排一次充电。6. 我踩过的坑以及让模型更可信的几个实操建议6.1 最经典的翻车随机参数没有物理边界我有一次图省事直接用randn生成环境温度结果是仿真样本里出现了 40℃ 和 -30℃ 同时大量存在的情况最后绘制的直方图两边长尾特别夸张整条温度曲线跟实际季节完全对不上。从那以后凡是物理量有明确边界的我都用均匀分布、截断正态分布或者直接用实测经验数据。仿真里允许出现不合理的样本错的不一定是模型但一定影响结论置信度必须从源头防住。6.2 参数相关性低温与暖风是一个组合不是两个独立事件前面提过低温与暖风的相关性。我第一版模型把温度设为随机变量辅助负载也独立采样结果跑出来一组“零下 10℃但辅助负载只有 1 kW”的样本。这在实际生活里几乎不会发生。解决办法也不复杂先把温度采样然后让辅助负载分布的条件期望跟着温度走比如temp -10 20 * rand; if temp 0 auxLoad 4 2 * rand; % 天冷暖风必然开大 else auxLoad 1.5 1.5 * rand; % 天热或春秋空调温和 end蒙特卡洛不是要求你把所有因素都独立随机化合理的做法是让因素之间保持真实世界的耦合。这一步做不做对尾部结果影响特别大。6.3 校准永远是最重要的一步用真实行程回流模型模型再漂亮没有实测数据校准就是自嗨。我建议至少找 1015 条真实行程记录包括出发 SOC、实际到达 SOC、环境温度、辅助负载大概水平。把这些输入喂回仿真模型生成对应到达 SOC再与真实到达 SOC 对比。如果偏差都在 5% 以内模型基本可信如果系统性偏低或偏高就要回头检查 η、滚阻系数和辅助负载分布是否合理。另外温度系数不能只靠一个插值表拍脑袋最好用同一款车在夏天和冬天的数据分别标定。我自己的经验是温度影响的斜率比想象中陡尤其 0℃ 附近变化很快。插值表里面的节点值得花一点时间用实测值替换。6.4 扩展方向加入排队、电池老化和动态电价这套蒙特卡洛框架的扩展性很好。我后来在充电时间模块里加入了充电桩排队模型把随机到达的车辆数和每个桩的占用时长做成二次蒙特卡洛于是充电站吞吐量评估也能做。电池老化方面给 SOH 加一个随使用里程衰减的过程就能预测几年后同一段出行路线的 5% 分位续航会掉到什么水平。动态电价则是在充电时间计算之后再叠加一个电费成本分布用来算不同充电策略的费用期望。如果你要把这些全部塞进同一个模型建议保持模块化能耗计算、充电曲线、排队过程各自独立成函数主循环只负责采样和调用。这样模型复杂度增加时不至于把调试变成灾难。从我自己的经验来看蒙特卡洛 Matlab 的组合在电动车辆续航这类多随机变量问题上确实是性价比很高的做法。核心成本不在代码而在输入分布的建模和校准。只要这两步做扎实结果能给你的决策带来相当大的确定性——这里的确定性指的是对不确定性的量化而这才是蒙特卡洛真正该有的价值。
返回列表