
前阵子接了个区域充电基础设施规划项目甲方开门见山这片区域未来两年新增5000辆电动汽车晚高峰的充电负荷曲线大概什么样能不能给个区间范围这个问题放在电动汽车充电负荷预测的框架下最实用的工具之一就是蒙特卡洛方法而我通常用Matlab快速把它跑通。写这篇文章的人是我自己跟你一样每天面对数据太少、又要结论的尴尬所以这篇东西算是一次完整的复刻记录覆盖原理、模型、代码、结果和踩坑适合做充电设施规划、电网负荷评估、或者写论文需要基础负荷曲线的朋友直接参考。1. 为什么充电负荷预测选择蒙特卡洛先把问题本质说清楚1.1 充电负荷预测的本质是大量随机行为的聚合电动汽车充电负荷和传统居民负荷最大的区别在于你几乎没法用几条典型曲线去套。传统负荷有相对稳定的用电习惯早中晚三个峰基本固定而电动汽车充电行为高度离散什么时候充、充多久、功率多大取决于车主几点下班、今天跑了多远、剩多少电、用快充还是慢充。换句话说单辆车的充电行为是个随机事件但整个车群的聚合负荷又有统计规律。这就像早高峰地铁客流你没法预测某一秒某个人会不会刷卡进站但100万人里有多少人在7点到9点之间进站这个分布是稳定可预测的。充电负荷预测关心的正是这种群体统计特征。1.2 蒙特卡洛在这个问题里扮演什么角色蒙特卡洛方法说白了就是大规模随机抽样实验。你先把每辆车的充电行为描述成若干概率分布然后用随机数按分布抽样模拟出每一辆车的充电过程最后把成千上万辆车的结果叠起来就得到一条总负荷曲线。重复多次之后还能算出均值、方差、置信区间。它不是一个预测算法而是一种统计实验框架。这一点很多人会误解以为蒙特卡洛是某种机器学习模型其实它不需要训练也不需要历史数据拟合核心输入是概率分布模型和物理约束。对充电负荷这种样本个体行为已知、群体结果未知的问题它特别合适。1.3 为什么不用时序预测或机器学习我也被问过很多次现在深度学习这么火为什么不直接用LSTM或者Transformer做负荷预测如果你的目标区域有三年以上的历史充电负荷数据而且未来场景和过去类似那机器学习方法完全可行精度还可能不错。但真实项目里经常遇到的情况是充电桩刚建没有历史数据要评估未来两年渗透率翻倍这种外推场景历史数据根本覆盖不到需要解释负荷峰值从哪来、哪个参数涨了会导致峰怎么移纯数据驱动方法很难给出物理解释项目周期短没有时间清洗数据和调模型。蒙特卡洛是自下而上的生成式方法你改一个参数比如快充比例、平均通勤里程结果能直观反映出来这对规划决策非常友好。方法数据需求场景外推能力可解释性实现成本时序分析法高弱中中机器学习很高弱低高蒙特卡洛模拟低只需分布参数强高低所以我的选择很明确规划评估类问题优先蒙特卡洛运行优化类问题才考虑机器学习。2. 负荷模型怎么搭从单台车到规模化车群2.1 单台车的充电行为拆解要把一辆车的充电过程写进程序需要回答四个问题什么时候开始充开始充的时候电池剩多少电充电功率多大充多久这四个问题对应的就是四个核心随机变量/物理量起始充电时间 (T_{start});起始SOC荷电状态由当日行驶里程反推充电功率 (P)取决于充电方式充电时长 (D)由所需电量和充电功率计算得出。2.2 三个核心随机变量的分布设定日行驶里程大量出行调查数据表明私家车日行驶里程近似服从对数正态分布右偏、非负尾部代表偶尔跑长途的车主。我常用的参数是均值35km、标准差取0.5对数域对应社会上家用车的平均通勤强度。这个分布的意义是直接把车主今天跑了多远和电池剩多少挂钩比凭空假设SOC分布更符合物理逻辑。起始充电时间家用慢充场景下车主通常下班回家后插枪所以起始时间集中在18:00-21:00。我习惯用均值19点、标准差3小时的正态分布近似同时截断到[0,24]区间。要注意的是这个分布不同城市差异很大一线城市通勤时间长峰值可能后移到20点有峰谷电价政策的区域用户可能刻意延后到22点以后。充电功率慢充通常取3.5kW或7kW家庭交流桩快充取30-60kW直流桩。实际项目中我会按慢充为主、快充为辅设定比例比如90%慢充10%快充再按期内车型结构细化。2.3 充电时长与SOC计算逻辑已知当日行驶里程 (L)、百公里耗电 (E_c)、电池容量 (C_{bat})起始SOC为[ SOC_{start} 1 - \frac{L \times E_c / 100}{C_{bat}} ]如果计算结果小于0说明行驶里程超出了续航按0处理实际中这种车大概率中途补过电但在简单的家用慢充模型里先忽略。接着判断是否需要充电我一般设一个阈值比如SOC低于0.8才充避免满电回家还插枪的无意义充电。需要充入的电量为[ E_{need} (1 - SOC_{start}) \times C_{bat} ]充电时长为[ D \frac{E_{need}}{P \times \eta} ]其中 (\eta) 是充电效率通常取0.9。注意这里 (E_{need}) 是电池侧能量(P) 是电网侧功率不乘效率会低估实际用电量。2.4 为什么这套模型够用很多人会纠结要不要把充电曲线做成恒功率-恒压两阶段要不要考虑温度对充电效率的影响要不要模拟电池衰减。我的看法是负荷预测关心的是电网侧功率随时间变化在1小时甚至15分钟的时间尺度上恒功率假设已经足够。电池侧的BMS策略再复杂最终反映到电网上的功率曲线在慢充场景下就是一条方波精确到每辆车的细节反而会增加计算量且未必提升精度。先把主干跑通再在敏感参数上做精细化这才是工程做法。3. Matlab代码实现从骨架到功能模块的完整做法3.1 整体架构设计我用Matlab实现这套预测时代码结构分四层参数区、单日模拟区、重复试验区和可视化区。参数区集中放所有分布参数和物理参数方便批量改场景单日模拟区负责模拟一天内N辆车的充电过程重复试验区控制蒙特卡洛次数输出均值曲线和置信带。时间分辨率我习惯取15分钟也就是一天96个点。分辨率太细比如1分钟会导致单点随机波动大计算量也大太粗比如1小时会淹没负荷曲线的峰值特征不利于评估变压器容量。15分钟是行业里比较通用的折中值。3.2 核心代码单日模拟函数function [load_curve, charge_records] simulateOneDay(params) % params: 结构体包含所有模型参数 N params.N; % 电动汽车数量 num_slots 96; % 15分钟一个点一天96个点 load_curve zeros(1, num_slots); % 预分配记录数组 charge_records zeros(N, 3); % [起始时间(h), 充电时长(h), 功率(kW)] for i 1:N % 1. 抽样日行驶里程对数正态分布 mileage lognrnd(params.mu_mile, params.sigma_mile); % 2. 计算起始SOC soc_start 1 - mileage * params.E_consume / (100 * params.E_battery); soc_start max(0, soc_start); % 3. 判断是否需要充电 if soc_start params.soc_threshold continue; end % 4. 抽样起始充电时间正态分布截断到[0,24] start_hour normrnd(params.mu_start_time, params.sigma_start_time); start_hour mod(start_hour, 24); % 5. 抽样充电功率按快慢充比例 if rand params.fast_charge_ratio p_charge params.P_fast; else p_charge params.P_slow; end % 6. 计算充电时长 energy_needed (1 - soc_start) * params.E_battery; duration_h energy_needed / (p_charge * params.eta); % 7. 累加到负荷曲线处理跨午夜的情况 start_idx round(start_hour * 4) 1; dur_slots ceil(duration_h * 4); for j 0:dur_slots - 1 idx mod(start_idx j - 1, num_slots) 1; load_curve(idx) load_curve(idx) p_charge; end charge_records(i, :) [start_hour, duration_h, p_charge]; end end这里有三个细节值得说明。为什么用对数正态分布抽样日行驶里程因为lognrnd天然不会生成负值且右偏特性符合实际——大多数车跑30-50km少量车跑100km以上。如果用普通正态分布会有一定概率抽到负里程不符合物理意义。为什么起始充电时间用mod而不是截断理论上应该用截断正态分布把抽到[0,24]之外的值丢弃重抽。但丢弃重抽效率略低而mod会把凌晨3点抽到的值折叠到下午3点附近这在晚上8点均值、标准差3的情况下极少发生抽到0点附近的概率不到万分之一所以工程上直接用mod是可以接受的。严格学术项目建议用截断抽样。为什么充电时长向上取整(ceil)充电时长是连续值但负荷曲线按15分钟离散化。取ceil意味着把零头算满略微高估峰值取floor则低估。我一般取ceil因为在规划场景中偏保守能避免变压器容量预留不足。3.3 蒙特卡洛主循环与置信区间计算单日模拟只代表一种可能性因为随机抽样每次结果都不同。要得到稳定的预测曲线必须重复很多次% 参数配置 params.N 1000; params.mu_mile log(35); params.sigma_mile 0.5; params.E_consume 15; % kWh/100km params.E_battery 60; % kWh params.soc_threshold 0.8; params.mu_start_time 19; params.sigma_start_time 3; params.fast_charge_ratio 0.1; params.P_fast 50; % kW params.P_slow 7; % kW params.eta 0.9; % 蒙特卡洛主循环 M 300; % 重复次数 num_slots 96; all_profiles zeros(M, num_slots); for m 1:M [load_curve, ~] simulateOneDay(params); all_profiles(m, :) load_curve; end % 统计均值曲线和95%置信区间 mean_profile mean(all_profiles, 1); std_profile std(all_profiles, 0, 1); ci_lower mean_profile - 1.96 * std_profile / sqrt(M); ci_upper mean_profile 1.96 * std_profile / sqrt(M); % 画图 time_axis (0.5:1:96) / 4; % 每点对应的小时时间 figure; plot(time_axis, mean_profile, b-, LineWidth, 2); hold on; fill([time_axis, fliplr(time_axis)], ... [ci_lower, fliplr(ci_upper)], b, FaceAlpha, 0.2, EdgeColor, none); xlabel(时间 (h)); ylabel(充电功率 (kW)); title(电动汽车充电负荷蒙特卡洛预测); legend(平均负荷, 95%置信区间); grid on;这段代码跑完你会得到一条带置信带的一日负荷曲线。M取300次是我实测后觉得性价比最高的值再往上加到1000次均值曲线变化很小但耗时线性增加。3.4 关于Matlab运行环境的补充用过Matlab的同学都知道不同版本在随机数生成函数上略有差异我本人在R2022b上测试过这段代码整体流程在更早的R2018a之后都能正常跑。如果你的机器上还没有合适的运行环境装好Matlab并确保Statistics and Machine Learning Toolbox在列就行因为lognrnd、normrnd这些函数都挂在这个工具箱下面。新版安装包里含有免费工具箱装的时候勾选统计工具箱即可。4. 典型结果与敏感性分析负荷曲线到底在说什么4.1 基线场景结果解读我按上面参数跑1000辆车的结果通常是这样负荷从中午开始缓慢上升17点以后明显抬升19点到22点之间出现峰值峰值功率在380-450kW之间即每辆车平均0.38-0.45kW的聚合功率系数凌晨2点后逐渐回落到很低的水平。这个曲线形状是合理的也符合很多已投运小区的实测规律。峰值不在19点而在20点前后是因为车辆到达时间有分散性而且充电过程是持续性的不同车开始充电的时间错峰叠加负荷峰值自然滞后于车主到家高峰。4.2 几个关键参数的敏感性实验我把参数逐个改一遍记录峰值变化结果很直观调整内容峰值变化原因车数从1000变到2000峰值近似翻倍充电负荷与车辆数线性相关起始充电时间均值从19点变到21点峰值略降峰现时间后移约2小时充电行为分散到更晚时段同时夜间谷段充电增加快充比例从10%提到40%峰值明显上升深夜负荷显著增加快充功率大同时快充行为不受到家时间约束日行驶里程均值从35km提到50km峰值上升约12%行驶里程增大→SOC降低→单次充电电量增加这些敏感性结果对规划非常有用。比如你发现某片区年轻居民多、下班晚那就要把起始充电时间均值调后如果片区内没有固定车位、用户依赖公共快充那就要提升快充比例。这套模型最适合的用法就是“调参数出结论”而不是找个固定值走天下。4.3 置信区间的实际含义很多人拿到置信区间后不知道怎么看。简单说置信带越窄说明预测结果越稳定也就是1000辆车的行为随机性被充分抵消了置信带越宽说明随机性还很明显需要更多样本或更精细的分布建模。实际项目中我更关注均值曲线同时用置信带上限做配电容量校核——毕竟变压器选型要按最不利情况留裕度。5. 工程化落地收敛性、效率与可复现5.1 样本量和重复次数怎么定蒙特卡洛里有两个样本数容易混淆单日模拟里的车辆数N和重复模拟次数M。N由实际场景决定你想评估的区域有5000辆车就写5000。M决定统计精度经验值是先跑50次看置信带宽如果带宽超过均值10%再逐步加到100、200次直到带宽稳定。盲目把M加到5000次不会提升准确性只会让你的电脑风扇狂转。5.2 随机数种子让结果可复现做项目最怕一种情况上午跑的结果和下午跑的结果不一样客户问你哪个准。所以代码开头我强烈建议加一行rng(20240501); % 固定随机数种子保证结果可复现种子的具体数值不重要重要的是固定下来。这样不管谁拿到你的脚本跑出来的曲线完全一致交付和评审都方便。但有一点要注意如果你在循环里换参数、改变量数量即使固定种子结果也会变这是正常现象。5.3 性能优化别再傻傻写三层循环最原始的写法是外层蒙特卡洛循环、中层车辆循环、内层时间片累加1000辆车跑300次就是30万个循环Matlab跑起来要很久。我在实际项目中做了两个优化第一能用向量化就不用循环。一只车的时间片累加循环可以改造为先算出所有开始索引和持续槽位数再用accumarray一次性聚合。% 向量化聚合示例节选 start_idx_all round(start_hours * 4) 1; dur_slots_all ceil(durations * 4); % 生成每辆车的占用电平下标 idx_list []; for i 1:N idx_list [idx_list, mod(start_idx_all(i):start_idx_all(i)dur_slots_all(i)-1, 96) 1]; end load_profile accumarray(idx_list, 1, [96, 1]) * params.P_charge;第二避免在循环里动态增长数组。预分配数组zeros、ones是老生常谈但很多人还是会忘记导致循环越跑越慢。5.4 数据输入输出的实用技巧真实项目里车辆数、分布参数往往不在代码里写死而是从Excel或者CSV读入。我会用readtable读取片区信息再用一个循环为不同片区生成不同参数的结构体。输出侧用writetable把mean_profile和置信区间写回Excel方便后续接入更大的规划报告。这里给个建议输出文件命名带上日期和参数版本号比如load_forecast_v1_2000cars.xlsx不然项目迭代几次你自己都分不清哪个是最新版。6. 我踩过的坑和给后来者的建议6.1 跨午夜的魔鬼细节最早一版代码我偷懒没有处理跨午夜问题直接用start_idx j递增结果所有晚9点以后开始充电的车负荷序列末尾被截断了凌晨的负荷被低估了30%以上。后来改用mod取余问题才解决。这里也提醒你画图的时候横轴要处理好连续性否则你会看到午夜处负荷突兀断裂看起来像bug其实就是索引边界没处理干净。6.2 分布尾部别放任不管对数正态分布偶尔会抽到很大的里程值比如300km这在数值上合法但一般情况下这种车主中途一定快充过不会顶着极低SOC回家再慢充。如果不加处理会产生一个特别长的充电时长把深夜负荷拉得异常高。我通常会对里程做上限截断比如超过300km按300km算或者超过续航两倍的样本直接重抽。虽然会在统计上产生一点点偏差但比产生幽灵负荷要合理得多。6.3 时间片选择会影响峰值结果同一组数据用15分钟分辨率算峰值是410kW用1小时分辨率算峰值可能只有360kW这不是bug而是平均效应。给电网提数据的时候一定要说清分辨率否则对方按1小时数据配变压器可能实际15分钟峰值已经超载了。我的一般原则规划校核用15分钟趋势分析可以放宽到1小时。6.4 快充比例是最大的不确定性来源跑敏感性时你会发现快充比例对负荷曲线形状的影响比任何单一参数都大。但现实中这个比例很难拍准因为它取决于充电桩建设进度、用户使用习惯、甚至油价波动。我的处理办法是设置三个场景保守快充5%、基准快充15%、激进快充30%三个场景都输出给甲方让人家自己判断风险偏好。6.5 进一步扩展的方向这套基础框架跑通之后往上加东西比较容易可以加温度和空调能耗修正系数可以加峰谷电价下的充电策略调整也可以改造为多日连续仿真只需要把SOC初始化改为承接前一天结果。做学术研究的可以在分布参数上做参数辨识用实测数据拟合出更准确的分布做工程咨询的可以直接在模型里导出台区配电变压器负载率报表。蒙特卡洛的框架就像个插件容器主干不用动反射到不同需求上很灵活。我从第一次跑通这段代码到现在已经拿它做过小区配网评估、高速服务区充电站规划、写字楼停车场慢充策略比选每次都是改改参数、调调分布、看看置信区间快速给结论。这套方法最吸引我的地方不是精度多高而是每一步的输入输出都能讲清楚道理客户问为什么这里有个峰你可以追到因为平均下班时间19点、充电时长2小时而不是扔出一句模型算的。做工程的人应该都有同感这种可追溯、可解释的感觉比黑盒预测结果踏实得多。如果你正准备搭自己的负荷预测工具别急着上复杂模型先把这篇里的核心循环跑通再逐步加自己的场景细节这条路走得最稳。