资讯详情

资讯详情

蒙特卡洛模拟在电动汽车充电负荷预测中的Matlab实现

2023年我接过一个小区充电负荷评估的需求。客户那边只给了一个Excel里面是三百多辆私家车的品牌型号外加一句“帮我们看看变压器会不会过载”。我第一反应是这事不能靠经验拍脑袋因为充电负荷和空调负荷不一样它完全由人的行为驱动——几点到家、今天跑了多远、插不插枪、插上充多久全是随机事件。后来我用蒙特卡洛把这件事拆成了“随机抽样大量模拟统计输出”的标准范式在Matlab里用不到两百行代码就跑完了500次仿真。这篇文章就是把那次完整的思路和代码拆开来讲。内容包括蒙特卡洛在电动汽车充电负荷预测里到底解决了什么问题、输入参数怎么从历史数据里标定、Matlab代码怎么写、模拟次数怎么选、结果怎么解读以及我实测中踩过的几个坑。适合正在做微电网、充电站规划、小区配变容量评估这类工作的人参考。你只要能跑通Matlab基础脚本就能跟着复现。1. 充电负荷为什么这么难预测蒙特卡洛要解决的随机性根源1.1 一条看似简单的充电曲线背后藏着至少四层随机性一台电动车慢充一小时大概消耗7度电一百台车同时充就是700kW的负荷这个数字可以让一台普通台区变压器直接告警。问题在于这100台车永远不会“整齐划一”地同时充。理论上我们可以把充电负荷拆成“车辆接入时间 × 充电功率 × 充电时长”这三件事的乘积。可这三件事每一件都带着随机性车辆接入时间用户几点回到家、几点把充电枪插上取决于下班时间、路上拥堵程度、是否要去超市买菜。起始SOC和需充电量今天跑了30公里还是200公里直接决定了电池还需要吃进多少电。车主不可能每天跑出完全相同的里程。充电功率慢充桩有3.3kW、7kW快充桩有60kW、120kW就算同一个桩电池在不同SOC区间的接受功率也不一样。充电持续时间由需充电量和充电功率共同决定本质上是前两个随机变量的函数。所以说充电负荷预测不能叫“预测”它本质上是在算“多个概率分布的卷积”。你想用一条确定的公式去描述它基本不可能你能做的是描述它“大概落在哪个范围、有多大可能超出某个阈值”。这正是蒙特卡洛方法的用武之地。1.2 确定性方法为什么算不准蒙特卡洛的思路转变传统的估算方法很粗暴取一个平均日行驶里程除以平均百公里电耗得到平均充电电量再乘以一个“同时率系数”最后摊到一天24小时上。这样做的好处是快坏处是它只输出一个“均值场景”完全抹掉了极端场景。举个例子。某个工作日大家正常通勤晚上7点左右回家负荷高峰在20:00。但如果某天是长假前一天很多人第二天要跑长途晚上10点还会集中去充电此时负荷峰值可能比均值场景高30%以上。更麻烦的是这种极端场景不是小概率事件它每年总会出现那么几十次而配变扩容、储能配置恰恰要按这种场景去校核。蒙特卡洛的思路完全不同它不去求唯一答案而是按照每个随机变量的概率分布去大量抽样一组样本就是“一个可能发生的日子”。把几千个“可能发生的日子”叠加起来就能看到完整的结果分布——均值是多少、5%分位在哪、95%分位在哪。一句话概括确定性方法回答“未来大概是什么样”蒙特卡洛回答“未来所有可能长什么样以及每种可能有多大”。对于充电负荷这种受用户行为支配的对象第二种回答方式显然更接近工程决策的真实需求。2. 建模前的数据准备用Matlab把模糊场景拧成概率分布2.1 车辆参数与用户行为参数怎么定蒙特卡洛仿真的输入不是单一数字而是一组概率分布。参数怎么定义直接决定结果可信度。我习惯把参数分成两类一类是“车自身说了算”的硬件参数另一类是“用户行为决定”的随机参数。硬件参数相对好办从车辆铭牌和充电桩类型就能拿到。随机参数则需要靠历史数据或合理假设来标定。下面是我在居民区慢充场景下常用的一套参数表你可以当成起点换成自己的数据随机要素分布假设典型参数说明日行驶里程对数正态期望30km对数标准差0.4里程恒为正数且有少量一天跑200km以上的长尾到家时刻正态分布均值18:00标准差1.8h如果数据更细可用分段正态或双峰高斯插枪延迟正态分布均值0.5h标准差0.3h用户到家后不一定马上充电充电功率离散分布7kW慢充为主快充桩需单独建模型充电效率常数0.9AC-DC转换和电池内阻损耗为什么日行驶里程要用对数正态因为行驶里程不可能为负而且分布有明显的右拖尾——大多数通勤日跑20到50公里但偶尔会有长途出行跑到一两百公里。对数正态分布天然满足“正数右偏”这两个特征而且Matlab里用lognrnd抽样非常方便。为什么到家时刻用均值18:00的正态分布因为大部分居民区用户是下班回家充电晚高峰集中在17点到20点。如果研究的是商业区或高速服务区这个分布要完全换一套参数甚至要用多个正态分布叠加成多峰分布。2.2 用fitdist做参数拟合从历史数据到分布对象如果手里有历史充电订单数据不要直接拍参数。Matlab的fitdist函数可以直接从数据里拟合出分布对象比自己猜参数靠谱得多。% 假设 mileageData 是从订单系统里导出的日行驶里程列向量 pd_mileage fitdist(mileageData, lognormal); mu_fit pd_mileage.mu; % 对数均值 sigma_fit pd_mileage.sigma; % 对数标准差 % 可视化看一眼拟合效果 figure; histfit(mileageData, 30, lognormal);拟合完之后要用histfit或者绘制经验CDF和理论CDF的对比图重点看分布尾部。我实测踩过一次坑某个网约车车队的行驶里程分布根本不是标准对数正态而是“工作日短、休息日长”的双峰结构。这种情况下强行套lognormal结果中段看着还行尾部完全失真。正确做法是用高斯混合模型gm fitgmdist(mileageData, 2); % 两个簇拟合双峰双峰模型会让深夜充电场景的出现概率更真实后续模拟的95%分位带也会宽不少。2.3 边界场景怎么处理快充桩、跨天充电、同时率很多人建模时只盯着“平均值”忽略边界场景结果仿真算出来的负荷曲线和实际台区数据对不上。有三个边界问题必须提前界定清楚。第一充电桩类型绝对不能混用。慢充桩7kW和快充桩120kW完全是两个物种。研究小区配变慢充为主研究高速服务区快充占绝对主导。如果在同一个模型里混着抽样负荷峰值会失真。我的做法是分开建模最后按比例叠加。第二跨天充电必须显式处理。晚上11点插上枪充到凌晨1点负荷会落在第二天的曲线上。代码里如果一刀切截断到当天24点会系统性低估凌晨负荷。所以要么把模拟区间拉长到36小时要么对跨天的部分单独存到第二天。第三同时率不需要额外假设。确定性方法里还要拍一个同时率系数而蒙特卡洛建模时只要正确抽样100辆车同时充电的场景会自动以一定概率出现。这就是概率方法的优势——你不用拍脑袋假设“同时率是0.3”而是让分布自己说话。3. 蒙特卡洛主流程与Matlab代码实现3.1 代码结构总览一个脚本拆成三段我写这类仿真通常不搞复杂的面向对象封装而是按照“参数配置—单日抽样—蒙特卡洛循环—统计输出”的顺序组织脚本。好处是调试方便改一个参数不用翻三层继承关系坏处是代码稍显冗长但对一百多行规模来说完全可以接受。整体逻辑是这样的设定车型参数、充电桩参数、分布参数、模拟次数。在每次模拟中为所有车辆抽样出“今天”的日行驶里程、到家时刻、插枪延迟。根据抽样结果计算每辆车的充电开始时间、充电时长生成一条单日充电功率曲线。把所有车辆的曲线叠加得到这一天的总负荷曲线。重复步骤2到4跑够设定的模拟次数。对多次模拟的结果做统计输出预期曲线和置信区间。代码里我最强调的是一个rng固定随机种子后面会细说。3.2 参数配置与单日抽样%% 固定随机种子保证结果可复现 rng(2026); %% 时间与场景参数 dt 15/60; % 时间步长15分钟一个点 T 24/dt; % 一天96个点 sim_case_num 500; % 蒙特卡洛重复次数 car_num 100; % 研究区域的电动汽车数量 %% 车辆与充电桩硬件参数 C_bat 60; % 电池容量 kWh P_charge 7; % 家用慢充功率 kW eff 0.9; % 充电效率 E_100 15; % 百公里电耗 kWh/100km %% 用户行为分布参数 mu_mileage 30; % 日均行驶里程期望 km sigma_mileage 0.4; % 行驶里程对数标准差 arrive_mu 18; % 到家时刻均值单位小时 arrive_sigma 1.8; % 到家时刻标准差 delay_mu 0.5; % 到家到插枪的延迟均值 delay_sigma 0.3; % 延迟标准差rng(2026)不是玄学。蒙特卡洛方法依赖伪随机数生成器如果每次运行不固定种子结果会每次不一样评审或者同事复算你的结果时没法比对。固定种子后500次模拟的结果可以一比一复现。这一点在工程项目里非常重要。3.3 单辆车的充电负荷曲线生成与叠加先抽一辆车的“今日状态”然后计算充电参数%% 抽样每辆车的充电需求 daily_mileage lognrnd(log(mu_mileage), sigma_mileage, car_num, 1); arrive_hour arrive_mu arrive_sigma * randn(car_num, 1); delay_hour delay_mu delay_sigma * randn(car_num, 1); % 充电开始时刻 到家时刻 插枪延迟 start_hour arrive_hour delay_hour; % 需要的电量 行驶里程 / 100 * 百公里电耗再考虑充电效率 energy_need daily_mileage / 100 * E_100; duration_hour energy_need / (P_charge * eff);这里三个抽样向量化了一次生成了100辆车的全部参数速度很快也方便后续调试。arrive_hour有可能抽出早上的值这是正态分布的尾部在真实中确实存在小部分夜班或凌晨回家的车主因此不需要人为裁剪。然后生成单辆车的充电曲线并叠加%% 生成单日总负荷曲线 load_profile zeros(1, T); for i 1:car_num % 计算充电起始点和持续的步数 start_step max(1, min(T, round(start_hour(i) / dt) 1)); dur_step round(duration_hour(i) / dt); end_step min(T, start_step dur_step - 1); % 固定功率充电累加到总曲线上 load_profile(start_step:end_step) load_profile(start_step:end_step) P_charge; end这段代码的关键是边界条件。start_step做了上下限保护避免凌晨到家算成负数、或深夜到家超出96步end_step做了一个截断如果超出当天就直接切到当天结束。截断会低估凌晨跨天充电的负荷我在5.2节再展开说怎么补。我在这里用的是固定功率充电模型也就是从插枪开始就以7kW持续充电直到电量充满。实际锂电池在恒流恒压CC-CV模式下末端功率会逐步下降。但在15分钟步长下我实测固定功率的误差很小工程上够用如果步长细化到1分钟末端下降就不可忽略了。3.4 多次模拟与统计结果输出单日曲线只是“一个可能的日子”。把上述过程放进蒙特卡洛循环跑完500次再对结果做统计%% 蒙特卡洛主循环 all_profiles zeros(sim_case_num, T); for sim 1:sim_case_num % 重新抽样每一天的车辆状态代码同上一节 daily_mileage lognrnd(log(mu_mileage), sigma_mileage, car_num, 1); arrive_hour arrive_mu arrive_sigma * randn(car_num, 1); delay_hour delay_mu delay_sigma * randn(car_num, 1); start_hour arrive_hour delay_hour; energy_need daily_mileage / 100 * E_100; duration_hour energy_need / (P_charge * eff); load_profile zeros(1, T); for i 1:car_num start_step max(1, min(T, round(start_hour(i) / dt) 1)); dur_step round(duration_hour(i) / dt); end_step min(T, start_step dur_step - 1); load_profile(start_step:end_step) load_profile(start_step:end_step) P_charge; end all_profiles(sim, :) load_profile; end %% 统计结果均值曲线、5%与95%分位带、峰值分布 profile_mean mean(all_profiles, 1); profile_p5 prctile(all_profiles, 5, 1); profile_p95 prctile(all_profiles, 95, 1); peak_all max(all_profiles, [], 2); peak_mean mean(peak_all); peak_p5 prctile(peak_all, 5); peak_p95 prctile(peak_all, 95); %% 绘图 t_hour (0:T-1) * dt; figure; hold on; fill([t_hour, fliplr(t_hour)], [profile_p5, fliplr(profile_p95)], ... [0.85 0.9 0.95], EdgeColor, none); plot(t_hour, profile_mean, b-, LineWidth, 1.6); xlabel(时刻h); ylabel(充电负荷kW); legend({5%~95%分位带, 均值曲线}, Location, NorthWest); grid on;prctile(all_profiles, 5, 1)的第3个参数1是让Matlab沿着“模拟次数”那一维求分位数返回的是一条96点的曲线。这个细节容易写错写成缺省的话Matlab会沿着第一维也就是96个时间点求结果就完全反了。跑完上面这段代码500次模拟在普通笔记本上大约两秒出头。如果车数量上万建议把内层for i循环改成矩阵切片操作不然循环会慢到让人怀疑人生。4. 模拟次数怎么定、结果怎么看一次仿真实测复盘4.1 从毛糙到收敛模拟次数对结果的影响蒙特卡洛次数不是越大越好也不是越小越省事。太小时结果曲线毛糙峰值的随机波动大你没法判断这个结果到底是真实趋势还是抽样噪声。太大时计算时间线性增长收益却微乎其微。我用上一节的参数做过一次收敛性测试结果大致如下模拟次数峰值均值kW峰值标准差kW相对用时50405550.2x100412380.45x300414291.2x500415272x1000415274x这组数据是特定参数下的示例不同分布参数会有差异但趋势一致次数从50加到500峰值均值变化不到3%峰值标准差从55降到了27再加到1000几乎不再变化。500次已经进入了“平台期”。需要强调的是收敛判断不能只看均值还要看95%分位。我遇到过均值曲线早就平了但peak_p95还在来回跳的情况。这说明当前抽到的样本对极端场景覆盖不足解决办法是增加模拟次数或者对极端场景做分层抽样确保尾部事件至少出现个几十次。4.2 峰值时刻与分位带解读拿上节的参数跑完500次典型的结果是这样负荷从傍晚18点开始抬升20点到23点之间达到峰值峰值均值大约在400多kW的量级5%到95%的分位带宽度大约在80到100kW之间。这里有个反直觉的点负荷峰值并不出现在“所有人同时到家”的18点而是延后了两三个小时。原因不难理解用户到家的时间本身就分散在17点到20点再加上插枪延迟真正开始充电的时间被整体向右平移而充电时长又普遍在2到4个小时所以功率曲线拖得很长。做配变容量规划时如果只盯晚高峰基荷曲线很容易漏掉20点之后的充电尖峰。分位带的解读也要说清楚。5%到95%分位带表示“在当前的随机性假设下90%的日子里负荷会落在这个区间”。工程上做扩容方案要盯95%分位甚至更高因为配变过载的代价远大于提前扩容的成本做充电费用结算或成本收益分析看均值曲线就够了。4.3 数据不好看时先查参数不查代码仿真跑完如果发现结果曲线和实际监测数据对不上我踩过的经验是先怀疑分布参数再怀疑边界条件处理最后才怀疑代码逻辑。95%的情况是分布参数没校准。举例来说如果你调研的社区其实是网约车司机聚集区用户深夜返回比例偏高而你还在用arrive_mu 18去抽样结果肯定偏低。这种偏差反映在曲线上不是整体平移而是“晚高峰凸起不够、深夜尾巴偏低”的形态差异。边界条件问题也很常见。跨天充电截断会导致凌晨负荷被低估而凌晨恰恰是谷电时段很多用户设置了预约充电实际负荷可能比均值模型高。这个问题需要回到5.2节专门处理。代码逻辑错误反倒少见因为一旦变量引用错了负载曲线会出现明显突变比如某个时段突然掉到零或者翻倍这种一眼就能看出来。5. 从“能出数”到“能落地”三个常见坑和处理经验5.1 随机变量被当成独立样本忽略真实相关性这是初学者最容易掉进去的坑而且结果看起来还挺合理的。抽样时我独立生成了daily_mileage和arrive_hour。但真实生活中这两个变量存在相关性上下班通勤的人通常傍晚回家行驶里程稳定在二三十公里跑长途的人回家时间更随机里程也更大。如果完全独立抽样会出现“今天跑了300公里长途、还18点准时回家”这种物理上说得通但概率极低的组合等于给仿真注入了不存在的噪声。处理办法有三种按成本从低到高排序。第一种简单过滤。抽样完成后对明显不合理的组合做剔除比如行驶里程超过200公里但到家时间早于16点的样本。这种办法粗暴但有效适合变量相关性不强的场景。第二种用copula建模相关性。Matlab的copularnd可以直接生成带相关性的多变量样本然后在边缘分布上做逆变换。适合对精度要求比较高的研究场景。第三种也是最容易被忽视的先算一下相关系数。用历史数据把daily_mileage和arrive_hour做一次corrcoef如果相关系数绝对值小于0.2独立抽样完全够用没必要花大代价上Copula。5.2 到家时间不等于充电开始时间插枪延迟必须建模我第一次搭建模型时图省事直接让start_hour arrive_hour结果仿真出来的负荷峰值比实际监测数据早了一截晚高峰的曲线形态也不对。后来翻了真实的充电订单时间戳才发现问题出在“插枪延迟”上。所谓插枪延迟是指用户到家之后到真正把充电枪插上之间隔的时间。这个时间在居民区场景里很可观有人要先吃饭有人要陪孩子还有人专门等到谷电时段才开始充。我统计过的样本里平均延迟在半小时左右标准差也不小节假日和周末甚至会超过1小时。把delay_hour加进来后负荷曲线的峰谷形状立刻对上了。这件事给我的教训是不要用直觉替代数据哪怕只是多花十分钟拉一下历史时间戳也比在会议室里争论“用户到底几点插枪”有用得多。另外有些地区出台了峰谷电价政策用户会自动把充电时间挪到晚上10点以后。这种“延迟”不是随机的而是强制的。建模时要在分布里加一个“谷电时段预约充电比例”参数否则你会严重高估晚高峰负荷。5.3 温度、电池非线性这些“学术参数”什么时候必须考虑固定功率模型、固定百公里电耗这种简化在大多数工程场景下够用但有两个场景必须升级模型。第一是温度影响。北方冬天电池活性下降加上车内采暖百公里电耗比常温高出20%以上。同一个用户夏天日均充电15度冬天可能要20度。如果项目是跨季节的全年评估需要把E_100设成温度的函数或者至少按“冬季参数”和“非冬季参数”分两套跑然后按月加权。南方地区可以忽略这一项但储能系统配置最好还是留出温度裕量。第二是充电末端的恒流恒压过程。固定功率模型假设从插枪到充满一直是7kW实际上锂电池充到80%以后会转入恒压阶段功率逐渐降低。在15分钟步长下这个误差很小但如果你把步长细化到1分钟或者模拟的是快充桩末端功率下降就不能忽略。处理办法是把充电功率写成SOC的分段函数效果更好一点的做法是用厂商给的充电曲线插值。我在实际项目里的折中方案是默认场景用固定功率模型校核极端场景时再切换到分段功率模型对比峰值差异是否在可接受范围内。大多数时候差异在5%以内但对快充占比高的场景差异能到10%甚至更高。再强调一遍整个项目我最大的体会蒙特卡洛不是一根“点石成金”的魔杖它的上限完全由输入分布的质量决定。把时间花在拟合历史数据、理解用户行为上远比纠结选哪种随机抽样算法更值得。每改一次参数拿一个已知月份的历史负荷曲线做回测偏差在5%以内再继续往下走。这套流程看着不惊艳但比任何高级算法都管用。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →