
电动汽车充电负荷的蒙特卡洛预测方法研究Matlab代码实现先聊点题外话。这几年越来越多同行来找我问电动汽车充电负荷预测的事问得最多的不是蒙特卡洛是什么而是我论文里的仿真图到底怎么跑出来。蒙特卡洛的入门资料满天飞但大多数都是摆公式、画流程图真正能落到Matlab代码里的实操内容少得可怜。我最初做这个方向的时候为了调试一个分布参数的取值连续一周每天改完代码挂机跑数据那种滋味估计做电力方向研究的兄弟姐妹都懂。这篇东西就把我自己的做法完整拆开讲一讲。它是一套能直接在Matlab里跑起来的实现方案包含模型框架怎么搭、每辆车的行为怎么抽、聚合之后怎么统计以及几个我在实际调试中踩进去就出不来的坑。不管你是刚接触这个方向的研究生还是已经跑通基础版、想加复杂场景的同行这篇应该都能给你一些参考。1. 为什么精确预测充电负荷这么难从电动车主的一天说起1.1 开回家就插枪背后隐藏着大量随机性我最早接触这个课题时导师给了一组小区配变的实测负荷数据让我结合电动汽车渗透率做个负荷评估。我第一反应是这有什么难的无非是叠加一个充电负荷上去。但等我把数据按时间画出来才发现问题远没那么简单——真实世界中电动车主不是每天早上八点准时插枪的机器人。同一个小区里有人通勤50公里、有人就在附近上班开5公里有人习惯下班回家立刻充电有人等到晚上谷电时段才插枪有人用7kW的家充有人每隔两天去一趟办公园区的地下车库用快充。每一个因素都带随机性这些随机性耦合在一条24小时的负荷曲线上结果就是充电负荷根本不是一个固定值而是一个随时间变化的概率分布。这也是充电负荷预测和常规电力负荷预测最大的不同。常规负荷有多年历史数据打底趋势、周期都相对明确充电负荷受车主行为主导行为又受出行需求、充电桩分布、电价机制、天气甚至当天是否下雨影响你很难找到一个纯粹的确定性函数去描述它。这时候蒙特卡洛方法就派上了用场——它不追求精确描述每辆车的充电行为而是通过大量随机抽样把个体行为的不确定性转化为群体负荷的统计特征。1.2 解析法算不清蒙特卡洛为什么能行在我做过的几种方案对比里解析法和简化场景法都有明显短板。解析法需要对充电起始时间、充电时长、充电功率建立联合概率模型再通过卷积等手段求总负荷的分布。理论上很漂亮但一遇到充电功率随电池SOC变化快慢充混用多车型不同电池容量这些实际条件模型复杂度就迅速爆炸解到后面数学上很头痛。蒙特卡洛的思路则简单粗暴得多我把某辆车今天怎么充电看作一次随机实验实验次数足够多以后统计结果就会逼近真实分布。这个过程不要求你推导复杂的联合分布只需要回答三个问题——某辆车今天开多少公里、什么时候到家、用什么功率充电。每一个问题都有现成的统计数据或经验分布做支撑随机抽取即可。抽十万次、百万次聚合出来的负荷特征自然就稳定下来了。打个比方你要统计一个城市所有人中午吃什么。解析法是把每个人的口味偏好做全量调研再算人口加权蒙特卡洛是随机拦下一万个人问你今天中午吃啥只要抽样够随机够多这一万个人的回答分布和全城真实分布差不了太多。充电负荷预测就是这个逻辑。2. 蒙特卡洛预测的整体模型框架先搭骨架再填血肉2.1 模型输入哪三类参数决定了负荷形态在我实际搭建的模型里参数分成三大类每一类管一块事情。你拿到代码以后第一步不是运行而是先把这三块参数的物理意义搞清楚——参数是模型的骨架骨架歪了后面全白搭。第一类是车辆参数。包括电池容量kWh、百公里电耗kWh/100km、充电功率kW。这几项直接决定了单辆车从某个SOC充到满需要多久、消耗多少电量。电池容量我用的是当前主流家用电动车的区间40~80 kWh不同车型按比例配置百公里电耗一般取13~20 kWh/100km充电功率分两种慢充7 kW交流家充桩典型值和快充60 kW以上直流快充桩典型值快充占比通常取10%~30%这个比例对负荷尖峰影响很大。第二类是出行行为参数。核心是两个分布日行驶里程和起始充电时间。日行驶里程我采用对数正态分布拟合这个在很多文献里都有验证美国NHTS调查数据、国内的出行调查数据都能支持。参数大致是均值30~50 km标准差20~40 km具体取值要看模拟的城市规模和通勤特征。起始充电时间通常用正态分布或分段分布描述通勤族为主的场景下下班回家后集中在18:00~21:00插枪峰值大约在19:00前后如果有分时电价引导还会出现一个明显的谷电时段23:00以后充电高峰。第三类是充电行为参数。主要是一个判断阈值车主在SOC低于多少时才会充电。我常用的做法是设一个随机阈值比如20%~50%均匀分布有些文献也会直接取固定值30%。这里注意一点起始充电SOC不能只看日行驶里程还得把前一天剩余电量考虑进去。很多早期论文简化成每天从满电开始是不太合理的实际车主很少每天都把电充满。2.2 单辆车的充电负荷怎么算从SOC到功率曲线框架搭好以后核心计算单元是单辆车一天的充电负荷曲线。我代码中的实现逻辑是这样的某辆车当天日行驶里程 d ~ lognrnd(mu, sigma) 起始充电时间 t0 ~ normrnd(mu_t, sigma_t)或按分段分布抽样 电池容量 C_battery按车型配置 百公里电耗 E_consume 出发前SOC_init 1 - d * E_consume / C_battery 若SOC_init SOC_threshold充电阈值 充电起始SOC SOC_init 需要充电量 (1 - SOC_init) * C_battery 充电时长 需要充电量 / 充电功率 若充电功率P是固定值 负荷曲线在[t0, t0充电时长]区间内取P 否则 按充电功率随SOC变化曲线分段计入 否则 当天不充电负荷曲线全0这里有两个我后来才想明白的细节。第一SOC_init的计算必须用日行驶里程除以续航而不是简单扣一个固定百分比。日行驶里程服从对数正态分布那么SOC_init也带随机性这才是蒙特卡洛的本质——把随机性从源头注入而不是在结果上拍脑袋。第二充电时长的单位是小时但负荷曲线的时间分辨率通常取15分钟或30分钟因此充电时长落在哪个时间网格里需要做取整处理不能直接拿浮点值当索引。我一般用15分钟分辨率一天的曲线就是96个点这个粒度在后续聚合和画图时都比较方便。充电功率是否随SOC变化取决于你研究的问题深度。简化模型中恒功率充电已经够用精细模型中锂电池在低SOC段功率受限高SOC段又会出现降功率这一块可以用一个简单的分段函数来近似。我通常的做法是SOC在20%~80%区间功率恒定低于20%功率打8折高于80%线性降功率到60%。这样既不会让模型太复杂又比纯恒功率更贴近实测充电曲线。2.3 从单辆车到千辆车聚合与重复模拟的逻辑单辆车算完了剩下的就是两层循环。第一层循环是车辆数量循环。比如模拟1000辆电动汽车就先把每辆车的96点负荷曲线存在一个矩阵里行是车辆编号列是时间点然后按列求和得到1000辆车一天的总充电负荷曲线。这一步本质上是把单体随机行为做统计聚合——单辆车的负荷曲线可能参差不齐但足够多辆车加总后总负荷曲线会呈现稳定的双峰或晚高峰特征。第二层循环是蒙特卡洛重复循环。同一组参数下我重复跑N次比如1000次每次都会得到一条总负荷曲线。N条曲线在同一个时间点上的均值就是该时间点的期望负荷标准差就是该时间点的波动范围。最终可以画出带置信区间的负荷曲线带也可以统计峰值负荷的概率分布。代码骨架大致是这样for mc 1:N_MC for i 1:N_EV load_profile simulateSingleEV(params); total_load(mc, :) total_load(mc, :) load_profile; end end mean_load mean(total_load, 1); std_load std(total_load, 0, 1);实际跑的时候车辆数乘以蒙特卡洛次数是总计算量。1000辆车乘以1000次就是100万次单辆车模拟Matlab纯循环跑会有点慢但后面我会说怎么用向量化和并行来提速。3. Matlab代码实现要点函数架构与核心代码解读3.1 参数模块怎么组织才不会改一次崩一片很多人写Matlab代码上来就把参数散在脚本各处跑通一次之后想改某个分布参数得满文件找。我的做法是单独写一个参数初始化函数或者结构体把所有模型参数集中管理。这样后面做敏感性分析的时候只需要批量改结构体里的字段值主程序一行都不用动。function params initParams() % 车辆参数 params.C_battery_range [40, 80]; % 电池容量范围[kWh] params.E_consume 0.18; % 百公里电耗[kWh/km]注意单位换算 params.P_slow 7; % 慢充功率[kW] params.P_fast 60; % 快充功率[kW] params.fast_ratio 0.2; % 快充占比 % 出行行为参数 params.mu_km 35; % 日行驶里程对数正态均值[km] params.sigma_km 0.6; % 日行驶里程对数正态标准差 params.mu_t0 19; % 起始充电时间均值[小时] params.sigma_t0 1.5; % 起始充电时间标准差[小时] % 充电行为参数 params.SOC_threshold_low 0.2; % 充电阈值下限 params.SOC_threshold_high 0.5; % 充电阈值上限 % 仿真参数 params.N_EV 1000; % 电动汽车数量 params.N_MC 500; % 蒙特卡洛重复次数 params.time_step 15; % 时间分辨率[分钟] params.T 24 * 60 / params.time_step; % 一天的时间点数 end这个结构体设计有个小细节日行驶里程的均值单位是km但百公里电耗我直接写成了0.18 kWh/km方便和电池容量kWh直接相乘避免百公里和km之间换算出错。这是一个非常容易踩的坑后面我在调试经验里会再提。3.2 随机抽样的核心函数写法和注意事项蒙特卡洛的核心是抽样函数这一段代码是整个程序的心脏。我用Matlab内置的随机数生成函数注意这几个函数分别的用途和参数含义lognrnd(mu, sigma)生成服从对数正态分布的随机数。注意入参是mu和sigma不是直接给均值和标准差。如果你手里的统计数据是日行驶里程的均值是35、标准差是12需要先换算成对数域的mu和sigma公式是mu log(m^2 / sqrt(v m^2))sigma sqrt(log(v / m^2 1))。我以前图省事直接用m和v当参数传进去跑出来的SOC分布完全离谱后来才意识到这个坑。normrnd(mu, sigma)生成服从正态分布的随机数。起始充电时间如果晚间为主我会把sigma取小一点避免太多样本落在白天如果模拟的是有分时电价的场景我会用分段均匀分布或者混合高斯分布而不是简单正态。unifrnd(a, b)生成[a,b]区间均匀分布随机数我用来抽充电阈值SOC_threshold和快慢充类型。抽样代码% 日行驶里程抽样 daily_km lognrnd(mu_ln, sigma_ln, N_EV, 1); % 起始充电时间抽样 start_time normrnd(mu_t0, sigma_t0, N_EV, 1); start_time mod(start_time, 24); % 归一化到[0,24) % 充电阈值抽样 SOC_threshold unifrnd(0.2, 0.5, N_EV, 1); % 快慢充类型抽样 is_fast rand(N_EV, 1) fast_ratio;这里有一个很重要的向量化技巧不要写for i 1:N_EV逐个抽而是直接用向量化随机数生成一次抽出一整列。Matlab的向量化运算比循环快一个数量级以上尤其是车辆数达到几千几万的时候差距非常明显。我后面所有代码都遵循这个原则。3.3 主循环、聚合统计与绘图输出抽样完成之后就是主计算循环。我建议把单辆车充电负荷曲线的计算封装成一个函数输入是这辆车的参数和抽样结果输出是96点的负荷向量。函数的好处是后面如果你想加温控负荷、换充电策略只需要改这个函数不影响外层蒙特卡洛结构。function load_curve calcSingleEVLoad(daily_km, start_time, SOC_thr, is_fast, params) C_battery unifrnd(params.C_battery_range(1), params.C_battery_range(2)); E_consume params.E_consume; % 计算到达时的SOC SOC_arrive 1 - daily_km * E_consume / C_battery; SOC_arrive max(SOC_arrive, 0.05); % 防止SOC低于0最低保底5% % 判断是否充电 load_curve zeros(1, params.T); if SOC_arrive SOC_thr if is_fast P_charge params.P_fast; else P_charge params.P_slow; end energy_needed (1 - SOC_arrive) * C_battery; charge_hours energy_needed / P_charge; % 将充电时长映射到时间网格 start_idx floor(start_time * 60 / params.time_step) 1; duration_idx ceil(charge_hours * 60 / params.time_step); end_idx min(start_idx duration_idx - 1, params.T); if start_idx params.T load_curve(start_idx:end_idx) P_charge; end end end这段代码有个很小的细节start_time单位是小时乘以60得到分钟再除以时间分辨率15分钟得到时间点索引。但Matlab索引从1开始所以1。同时要防止start_idx和end_idx越界最后一天充电充到24点之后要截断。这些边界条件不处理程序偶尔跑出负索引或者数组越界排查起来相当头疼。主程序需要汇总所有蒙特卡洛重复的结果。我通常把结果存成一个三维矩阵[蒙特卡洛次数, 时间点, 场景]。比如对比不同渗透率场景第三个维度就可以存不同渗透率下的结果后面画对比图非常方便。统计输出包括每个时间点的均值、标准差、5%和95%分位数还有每天的峰值负荷序列。绘图方面我一般画三张图第一张是1000辆车聚合后某几次模拟的负荷曲线叠加展示随机波动第二张是均值加95%置信区间带这是论文里最常用的图第三张是峰值负荷的直方图能直观看出最坏情况出现的概率。画图代码用fill函数画置信区间带用histogram画峰值分布效果比默认的plot好很多。4. 仿真结果分析负荷曲线长什么样多少次要跑才够4.1 典型场景下的充电负荷曲线解读我拿一组典型参数跑出来的结果是这样1000辆车慢充占比80%快充占比20%起始充电时间均值19点、标准差1.5小时日行驶里程对数正态均值35km、标准差0.6对数域蒙特卡洛500次。聚合负荷曲线呈现非常明显的晚高峰特征从18点开始负荷爬升19点到21点之间达到峰值22点以后逐渐回落。这个形状很好理解通勤族下班回家插枪充电在19点左右形成充电行为高峰由于慢充功率7kW、充电时长通常2~4小时负荷会持续到深夜但新接入的车辆减少总负荷自然回落。快充负荷则完全不同。它主要在白天出现随机散布在10点到17点之间单辆车功率大但持续时间短30~60分钟对总负荷的贡献呈现出更多毛刺。如果快充占比提高到30%晚高峰叠加的冲击会变得很剧烈——在19点前后过路车快充和回家慢充同时发生峰值负荷明显抬升。这里有一个重要的认知我们预测的不是一条曲线而是一条曲线带。500次蒙特卡洛模拟得到的同一天负荷曲线在19点这个时刻均值可能是800kW但不同次模拟之间标准差能到80~120kW95%置信区间上下浮动接近25%。这意味着如果配电网规划按均值做实际运行中大约有5%的概率负荷比均值高出25%以上。这个波动范围才是蒙特卡洛预测的真正产出比单一数值更有工程参考价值。4.2 蒙特卡洛次数如何收敛从标准差变化确定模拟次数很多论文直接写蒙特卡洛次数取1000次但没人告诉你这个次数怎么来的。我自己的做法是做收敛性测试跑不同次数50、100、200、500、1000、2000次观察关键指标的变化情况。我通常监控两个指标一是晚高峰峰值负荷的均值二是峰值负荷的标准差。随着模拟次数增加均值会逐渐趋于稳定标准差会缓慢下降。当次数从1000增加到2000均值变化不到1%标准差变化不到3%时我就认为已经收敛了不需要再增加次数。以我的典型参数为例500次时峰值均值和1000次的结果相差约2%已经可以接受但如果你要画置信区间做精细分析我建议至少跑到1000次。注意蒙特卡洛的收敛速度是1/sqrt(N)也就是说想提高一位精度计算量要增加一百倍。在实际研究中1000~2000次是精度和计算成本的平衡点不是越大越好。4.3 参数敏感性渗透率、快充占比的影响蒙特卡洛模型建好以后最常用的分析就是参数敏感性。我最常做的三个敏感性分析是电动汽车渗透率变化。这是配电网规划最关心的指标——渗透率从10%提高到50%峰值负荷怎么变由于充电负荷和车辆数成正比峰值负荷基本线性增加但注意当渗透率提高时充电负荷在总负荷中的占比变大总负荷曲线从晚高峰平缓变成晚高峰尖峰峰谷差急剧拉大。这个结论对配变扩容规划非常关键。快充占比变化。快充占比从10%提高到30%对峰值的影响不是简单的3倍放大因为快充和慢充的时间分布不同。快充随机散布在白天会抬高日间负荷水平慢充集中在晚间抬高晚间峰值。两者叠加会改变负荷曲线的形状而不是简单缩放。我做过的实验显示快充占比从10%提到30%峰值负荷大约增加18%~25%但峰谷差增幅更大因为夜间基础负荷低、充电行为少。充电阈值的变化。充电阈值SOC低于多少才充电影响充电频率。阈值从20%提高到50%意味着更多的车会在到家后充电负荷曲线整体抬高但抬高的幅度呈现出边际递减——因为阈值提高到一定程度大部分车每天都会充再提高就没有新增充电需求了。这些敏感性分析的价值在于它不只是给你几张图而是告诉你哪个参数最值得优化。比如如果发现快充占比对峰值影响最显著那在做有序充电策略时优先管理快充负荷就是性价比最高的手段。5. 我在实际调试中踩过的几个坑5.1 随机数种子和可复现性问题这是新手最容易忽视的问题。Matlab的rand、randn、lognrnd、normrnd这些函数默认每次运行时都会用不同的随机数种子导致你上午跑出来的结果和下午跑出来的结果完全不一样哪怕参数一丁点没改。写论文的时候这非常致命——你图都画好了第二天打开Matlab重新运行一次图变了审稿人复制代码跑一遍出来的结果和论文对不上。解决方案是在主程序最开始加一行rng(2024); % 固定随机数种子保证结果可复现固定种子之后蒙特卡洛模拟的结果就完全确定。我习惯用年份当种子数这样每次提交代码、保存结果时都有据可查。注意加了rng固定种子之后即使你修改了某个物理参数只要随机数生成顺序不变不同参数之间的对比仍然是公平的——因为抽样到的随机数序列相同差异完全来自参数本身。5.2 分布参数不合理导致SOC溢出或为负我最早跑模型时经常遇到一个诡异的现象SOC_arrive出现负数或者超过1。一开始我以为是公式写错了检查半天发现是对数正态分布的参数没换算对。麻烦在于lognrnd的输入参数是对数域的均值和标准差不是原始域的。如果你手上只有原始数据的均值和标准差比如日行驶里程均值35、标准差20直接传给lognrnd(35, 20)生成的数据均值可能是几百公里SOC秒变负数充电时长算出来能充三天三夜。正确的做法是先换算m 35; v 20^2; mu_ln log(m^2 / sqrt(v m^2)); sigma_ln sqrt(log(v / m^2 1)); daily_km lognrnd(mu_ln, sigma_ln, N_EV, 1);换算完之后抽样结果的均值和标准差才会落在你预期的范围内。我在所有需要从统计数据拟合分布参数的地方都做了这一步并且每次抽样后都会加一句assert(all(daily_km 0))做校验这样程序出错时能在源头停下来而不是等到后面出现负数SOC才一脸懵。5.3 数据量大步长细时性能瓶颈与并行优化当车辆数达到5000辆、蒙特卡洛次数达到2000次时纯循环的Matlab程序跑起来很吃力一次仿真要十几分钟甚至更久。我在这个阶段做了两个优化效果显著。第一步是向量化内层循环。把单辆车的计算从一车一次改成一批车一起算。Matlab的数组运算天然支持批量处理比如多个车的日行驶里程是一个N×1向量SOC_arrive也是一个N×1向量所有计算都可以一步完成只有最后的把负荷映射到时间网格这一步比较麻烦因为这个操作本质上是稀疏累加。我的做法是把所有车的起始索引、持续索引组装成索引矩阵用accumarray一次性累加比for循环快四五倍。第二步是并行池。蒙特卡洛的重复次数是天然的可并行任务——每一次模拟之间互不依赖。Matlab里只需要把外层循环改成parforparfor mc 1:N_MC total_load(mc, :) simulateOneDay(N_EV, params); end注意parfor要求每个循环迭代之间完全独立不能共享变量、不能依赖循环顺序。我把每辆车的结果存进临时变量再在迭代内部完成聚合这样parfor就完全可用。我实测下来8核CPU上parfor比普通for快6倍左右受数据传输开销影响达不到8倍5000辆车2000次模拟从18分钟降到3分钟基本可以接受。不过这里提醒一句如果只是做课程设计或者论文复现1000辆车500次模拟的规模完全不用优化普通循环几秒钟就跑完了。优化的前提是确实有大规模仿真的需求别为了炫技把代码复杂度拉高。6. 从预测到应用这套模型还能怎么扩展6.1 与配电网潮流计算结合评估蒙特卡洛充电负荷预测的真正价值不只是画一条负荷曲线而是作为上层分析的输入。最常见的就是把预测得到的充电负荷叠加到典型日基础负荷上再输入到配电网潮流计算中评估节点电压偏移、支路负载率、变压器容量裕度。我自己的做法是把蒙特卡洛输出的均值曲线置信区间带分别作为潮流计算的确定性输入和边界情形这样既能看到典型运行状态也能看到极端场景下的电压越限风险。比如我做过一个算例某小区配变额定容量630kVA基础晚高峰负荷400kW叠加200辆电动车的充电负荷峰值约300kW后配变负载率达到110%明显过载。这个结论用传统确定性分析很难说清楚过载概率有多大但结合置信区间就能给出95%置信水平下配变负载率在95%~125%之间波动其中约30%的概率超过110%。这种概率化的过载风险是配电网规划单位最想要的结果。6.2 考虑温度和交通因素目前这套模型假设充电行为只受出行里程和时间影响但现实中温度和路况对充电负荷的影响不可忽视。低温环境下电池实际可用容量下降同样里程消耗的SOC更多同时低温充电功率受限充电时间拉长。这两种效应叠加冬季充电负荷比夏季高10%~20%是很常见的。我在后续版本中加入了温度修正系数——把电池容量和充电功率乘以一个随温度变化的系数例如0℃时容量系数0.85、功率系数0.825℃时两者都趋近1。还尝试把交通拥堵系数折算成额外的行驶里程消耗——堵车意味着同样的通勤距离消耗更多电量相当于日行驶里程乘以一个1.1~1.3的拥堵系数。这些修正让模型更贴近实际代价是参数多了、需要标定的数据多了。如果只是做基础研究可以先用简单版本把扩展点留成接口。6.3 用户分类与行为模式的进阶最让我觉得值得深入的方向是按用户类型拆分建模。通勤私家车、网约车、物流车、公交车它们的充电行为差异非常大网约车白天频繁快充物流车可能在凌晨集中充电公交车的充电时刻几乎完全固定。把它们混在一个分布里抽样会丢失大量行为特征。我的建议是设计一个用户类型参数层每种类型配置独立的车辆参数分布、出行行为分布和充电策略然后在模拟时按比例抽样用户类型。这样虽然代码结构复杂一些但可以把混合分布的问题转化为多组单分布的问题模型解释性大幅提升。我自己的最终版本里已经支持三类用户混仿运行时间只是线性增加结果比单一模型合理得多。最后分享一个我后期才养成的习惯每完成一次仿真我都会把参数结构体、随机数种子、主要结果指标打包保存成一个.mat文件文件名里带上日期和场景描述。这看起来是多此一举但等你一个月后回头写论文、补实验数据的时候会发现这个习惯拯救了无数次这个图是哪组参数跑出来的的崩溃时刻。蒙特卡洛模型的灵活性本身就是它的复杂性数据管理做不好再准确的模型也发挥不出价值。