ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

基于蒙特卡洛算法的电动汽车充电负荷模拟与Matlab实现

基于蒙特卡洛算法的电动汽车充电负荷模拟与Matlab实现 去年给一个片区做充电设施规划时电网那边直接抛了个问题过来这一片要是同时插上几千辆电动车配电变压器到底要按多少容量留我当时没有马上给数字因为这个问题不像算一栋楼最大负荷那么简单——每辆车几点回家、前一天跑了多少公里、电池剩多少电、用快充还是慢充全都不一样。这些“不一样”叠到一起才是真实会出现的负荷曲线。蒙特卡洛算法就是处理这种问题的把每辆车的行为按概率分布随机抽样模拟足够多辆车、足够多天之后把结果统计成一条负荷曲线。我这两年用Matlab把整套流程从零搭了一遍模型、代码、踩坑都在这篇文章里。文章适合正在做配电网规划、充电设施布局的工程师也适合课程设计或毕业论文需要“仿真结果”的研究生——你不需要蒙特卡洛背景只要会Matlab的基本语法跟着代码走就能跑出结果。1. 充电负荷为什么能“算出来”蒙特卡洛的适用逻辑1.1 负荷问题的本质大量独立随机行为的叠加单看一辆电动汽车充电负荷完全是一个随机事件几点到家、电量剩多少、充多久谁也说不准。但如果把几千辆车放在一起看规律就出来了——傍晚下班回家后顺手插上充电枪的人多了晚上自然形成一个用电高峰这和交通统计里的“通勤规律”是强相关的。所以充电负荷问题的本质是“个体随机、群体统计”。我们不需要精确知道某辆车几点插枪只需要知道“一个城市片区里在18:00到19:00到家并开始充电的车大概占多少比例”以及“它们平均还需要充多少电”。把这些比例和平均值换算成随机分布再反复抽样就能逼近真实的总体负荷形态。1.2 为什么是蒙特卡洛而不是解析公式有人会问既然有了概率分布能不能直接用数学卷积算出总负荷理论上可以但实际有几个障碍SOC计算有非线性低于下限就不再线性、充电时长要向上取整到时间步长、到家时间被截断在一天范围内、快慢充是混合分布……这些细节叠加在一起解析表达式会变得非常复杂而且每改一个假设就要重推一遍公式。蒙特卡洛的思路正好绕开了这个麻烦。它的处理方式是把每辆车的参数按分布随机生成出来逐辆计算出充电曲线然后累加。重复几百次之后随机波动被平均掉留下的就是稳定规律。背后有大数定律兜底——模拟次数越多均值越接近真实期望。这个方法不需要复杂的数学推导只需要把“规则”写对模型是透明的想加约束也很容易。1.3 算法主流程一览整套模拟的流程可以概括为六步我贴在下面设置研究范围与基础参数车辆数、电池容量、充电功率、时间步长等。随机生成每辆车的行为参数到家时间、日行驶里程。由行为参数推算每辆车的充电需求起始SOC、需要充入的电量、充电时长。把每辆车的负荷叠加到当天96个时间点上形成单日负荷曲线。重复第2到第4步N次得到N条日负荷曲线。对N条曲线逐点求均值、分位数得到期望负荷曲线和不确定性区间。步骤2里可以换分布步骤3里可以加充电策略步骤4里可以改时间步长整套框架不用动。后面代码也是按这个流程组织的。2. 模型里的“看不见的选择”四个概率分布怎么定建模里最影响结果的往往不是代码而是概率分布参数选得对不对。参数不对算法再精巧结果也失真。下面把四个关键分布的取法和依据挨个讲清楚。2.1 到家时间决定负荷最早什么时候出现私家车充电大多发生在“最后一次到家”之后所以我们关心的是通勤结束时间。大量城市出行调查显示晚高峰集中在17:00到19:00但有人加班、有人接娃、有人错峰所以在均值附近有一个较宽的分散。工程上常用正态分布描述到家时间服从 N(17.8, 2.5)单位是小时。也就是说均值在17.8点标准差2.5小时覆盖了从下午到深夜的大部分情况。在代码里我会把样本截断在[8, 24]小时之间避免出现凌晨三四点到家这种明显不符合通勤情景的异常样本。截断会影响分布尾部但这部分概率本身很小对总负荷影响可以忽略。如果你研究的场景是网约车或出租车这个分布必须换——网约车的高峰在后半夜还有一波和私家车完全不同。2.2 日行驶里程决定充电需求大小充电需求本质上由“今天跑了多少路”决定。城市通勤私家车的日行驶里程有明显右偏大部分车每天只跑20到40公里少数车会跑100公里以上。这种形态用对数正态分布拟合最合适。我这边取对数域参数 μ3.5、σ0.8换算回去中位数约33公里均值约46公里和不少城市的通勤调查报告能对上。如果你把这条分布画出来会看到大多数样本集中在几十公里附近尾部长长拖向远处这比用普通正态分布硬拟合要靠谱得多。在仿真中每辆车从对数正态分布里抽一个日行驶里程然后用它去算当天的起始SOC。这个分布直接决定充电电量的大小如果一辆车一天只跑15公里晚上基本上充不了多少电就满了如果一天跑了150公里那肯定要充几个小时。2.3 起始SOC与目标SOC从行驶里程到充电时长这里有一行核心公式我把它展开写起始SOC 出门SOC - 日行驶里程 / 标称续航公式里的出门SOC我设为0.9意思是早上出门时电池基本是满的但留了一点余量。标称续航取400公里对应现在市面上主流长续航车型。如果一辆车当天跑了50公里那么起始SOC等于0.9减去50除以400结果是0.775。和目标SOC0.9比需要补0.125也就是60kWh电池里的7.5kWh用7kW慢充充大约1.1小时。如果当天跑了200公里起始SOC掉到0.4需要补0.5也就是30kWh用7kW慢充得充约4.8小时。目标SOC我取了0.9而不是1.0。原因是很多用户并不会充满尤其快充时为了节省时间会充到90%左右就拔枪慢充场景下确实经常充满但作为通用建模假设0.9比较稳妥。代码里还设置了SOC下限保护如果按公式算出来的起始SOC小于0.1直接拉回到0.1。现实中很少出现这种极端情况但仿真时为了保证不出现负SOC必须有这个保护。2.4 快慢充比例与充电效率同样电量不同冲击充电功率档位决定了同样一车电对电网的“冲击力”。7kW慢充充一个多小时分散在很长的时间窗里60kW快充只需要十几分钟到半小时但瞬时功率是慢充的近9倍。我在基础场景里假设85%的车在家用慢充15%去公共快充桩。快充比例一高负荷曲线的峰形就会明显变窄变高这个在第4节会看到对比结果。充电效率取0.9主要考虑充电过程中的AC/DC变换损耗、电池内阻热损耗等。实际效率会随温度、SOC区间波动但宏观仿真里取一个固定值就够了。敏感性分析可以做但正常情况下0.85到0.95之间对曲线形态影响很小。所有参数汇总在下面代码里也是按这张表配置的。参数取值说明车辆数5000研究范围内电动汽车保有量电池容量60 kWh主流中型轿车电池标称续航400 km长续航车型水平出门SOC0.9为电池留余量目标SOC0.9充电停止SOC充电效率0.9含变换与线路损耗慢充功率7 kW家充桩/交流桩快充功率60 kW公共直流快充桩快充比例0.15基础场景假设到家时间N(17.8, 2.5)h截断到[8,24]h日行驶里程logN(3.5, 0.8)单位km时间步长15 min一天96个点模拟次数200蒙特卡洛重复次数3. Matlab代码从零到闭环函数实现与蒙特卡洛循环参数确定以后就可以写代码。我建议把“单日仿真”和“多次蒙特卡洛循环”分开。单日仿真负责生成一条日负荷曲线主程序负责重复调用并统计。这样改参数、换场景都不碰核心代码。3.1 单日负荷仿真函数这个函数的输入是一个结构体params输出是一条96维的负荷曲线。函数内部做的事情正好对应第1.3节的步骤2到4。完整代码贴在下面。function load simulate_one_day(params) % 单个仿真日的电动汽车充电负荷曲线 n params.num_vehicle; % 1) 日行驶里程对数正态抽样单位km daily_km lognrnd(params.dist_mu, params.dist_sd, n, 1); % 2) 到家时间抽样并转成slot编号 arrive_h params.arrive_mu params.arrive_sd * randn(n, 1); arrive_h min(max(arrive_h, 8), 24); % 截断 arrive_slot ceil(arrive_h * 60 / params.time_step); % 3) 充电功率档位按概率抽快充/慢充 is_fast rand(n, 1) params.p_fast; power ones(n, 1) * params.P_slow; power(is_fast) params.P_fast; % 4) 起始SOC与充电时长 soc_init max(0.1, params.pre_trip_soc - daily_km / params.range_km); delta_soc max(0, params.soc_target - soc_init); charge_h delta_soc .* params.battery_kWh ./ (power .* params.charge_eff); charge_slots ceil(charge_h * 60 / params.time_step); % 5) 累加到96个时间点 load zeros(params.slots, 1); for k 1:n d charge_slots(k); if d 0 continue; % 该车不需要充电 end s arrive_slot(k); e min(s d - 1, params.slots); load(s:e) load(s:e) power(k); end end稍微解释两个容易被忽略的细节。第一charge_slots用ceil向上取整意味着1.07小时的充电会被记成1.25小时总负荷会比真实值略大。但蒙特卡洛模拟本来就是基于统计假设的估算保守一点不是坏事而且取整后所有车辆都对齐到15分钟网格上后续处理方便很多。第二如果delta_soc为0charge_slots为0循环里直接跳过。这一步等价于“剩余电量比目标SOC高不需要充电”。3.2 主程序参数配置与200次蒙特卡洛循环主程序负责定义params结构体、固定随机种子、执行200次模拟并保存全部曲线。%% 参数配置 params.num_vehicle 5000; params.num_sims 200; params.time_step 15; params.slots 24 * 60 / params.time_step; params.battery_kWh 60; params.range_km 400; params.charge_eff 0.9; params.pre_trip_soc 0.9; params.soc_target 0.9; params.P_slow 7; params.P_fast 60; params.p_fast 0.15; params.arrive_mu 17.8; params.arrive_sd 2.5; params.dist_mu 3.5; params.dist_sd 0.8; rng(2024); % 固定随机种子 %% 蒙特卡洛主循环 all_curves zeros(params.num_sims, params.slots); for sim 1:params.num_sims all_curves(sim, :) simulate_one_day(params); end %% 统计量 mean_load mean(all_curves, 1); p5_load prctile(all_curves, 5, 1); p95_load prctile(all_curves, 95, 1); %% 绘图 t_center ((1:params.slots) - 0.5) * params.time_step / 60; figure(Color,w,Position,[100 100 880 480]); hold on; fill([t_center fliplr(t_center)], [p5_load fliplr(p95_load)], ... [0.85 0.9 0.95], EdgeColor, none); plot(t_center, mean_load, b-, LineWidth, 1.8); plot(t_center, all_curves(1,:), k--, LineWidth, 0.9); xlabel(时刻 / h); ylabel(充电功率 / kW); legend({5%~95%区间,多次模拟均值,单日样本},Location,northwest); xlim([0 24]); grid on;在R2023a上这套代码跑完200次模拟大概需要8到12秒取决于电脑配置。如果平时习惯用for循环写模型性能完全够用。想再快后文第5节有优化思路。时间步长我最终定为15分钟这是权衡后的结果。太细比如1分钟会让slot数从96涨到1440计算和存储压力大而且对配电网容量评估来说没必要太粗比如1小时会平滑掉晚高峰的短时冲击快充负荷被“抹平”影响容量判断。15分钟对多数规划场景够用也能看出明显的峰谷形态。3.3 几个关键代码行的原理lognrnd(params.dist_mu, params.dist_sd, n, 1)生成n个对数正态随机数。因为日行驶里程不可能是负值而且分布形态天然右偏用这个函数最直接。arrive_slot ceil(arrive_h * 60 / params.time_step)是把小时换算成slot索引。比如18.0点对应第72个slot18.25点对应第73个slot这样后面累加时就能对齐。load(s:e) load(s:e) power(k)是逐辆车把功率加到对应时间段上。如果多辆车在同一个slot充电功率自然累加这就是“聚合负荷”的形成过程。理解这一行就理解了整个蒙特卡洛累加的核心。4. 结果长什么样曲线、区间与敏感性对比代码能跑通之后先别急着换参数把基础场景的结果好好看一遍。4.1 基础场景下曲线是什么形态按上面参数跑出来的曲线大体上是这样的白天负荷几乎为零傍晚18点开始快速爬升19点到21点之间维持峰值平台之后缓慢回落到凌晨基本结束。峰值对应的功率在数千kW量级具体数值取决于车数和快充比例。这里最值得看的是灰色区间5%到95%分位区间和蓝色均值曲线的差距。灰带很窄说明在5000辆车的规模下单日之间的随机波动已经被统计平均抹平负荷曲线非常稳定。而黑色虚线——单日样本——虽然趋势大体相同但毛刺明显和均值之间有可见偏差。这就是蒙特卡洛的价值单次样本不能用于规划决策但多次模拟得到的均值和分位数可以。需要强调一点5000辆电动车对于一个小区来说太大了这里更接近一个街道或工业园区的规模。如果只算200辆车灰带会宽很多单日随机性明显规划时就得留更大裕度。这正是蒙特卡洛能让你直观看到的东西规模越小不确定性越高规模越大规律性越强。4.2 保有量变化规模如何放大负荷规划时最常问的问题是“如果电动车保有量翻倍负荷会怎样”。这个模型改一个数字就行。保持其他参数不变把num_vehicle分别设为1000、5000、10000各跑200次模拟把均值画在同一张图上。configs {N1000,N5000,N10000}; N_list [1000, 5000, 10000]; figure(Color,w,Position,[100 100 880 480]); hold on; for ci 1:3 params.num_vehicle N_list(ci); tmp_curves zeros(params.num_sims, params.slots); for sim 1:params.num_sims tmp_curves(sim, :) simulate_one_day(params); end plot(t_center, mean(tmp_curves, 1), LineWidth, 1.6); end legend(configs); xlabel(时刻 / h); ylabel(充电功率 / kW); xlim([0 24]); grid on;结果符合直觉三条曲线形状几乎一样峰值大致按车辆数线性放大。这说明在参数假设不变的情况下充电负荷的“大小”由车辆规模决定“形状”由用户行为决定。为配电网规划提供了一个直观参考不管片区多少辆车峰值时段基本锁定在晚高峰预留容量时重点看这个时段就行。4.3 快充比例变化峰值压力从哪来再来对比快充比例把p_fast从0.15改成0.3其他不变。结果峰值会明显抬高峰值时段更集中。原因不复杂快充功率60kW是慢充的近9倍即使只有15%的车用快充在峰值时刻它们的功率贡献也可能接近总负荷的一半。如果傍晚六点半到七点半之间一批网约车同时进快充站瞬时冲击会比普通私家车慢充大得多。所以在看仿真结果时不要只报一个总电量还要看瞬时功率。变压器和线路的容量是按瞬时功率留的不是按总电量。快充比例这个参数在规划公共充电站时比车辆保有量还敏感值得专门做敏感性分析。5. 仿真中最容易翻车的细节踩坑记录与扩展思路代码看着简单实际跑起来还是有几个容易翻车的地方。我把自己踩过的坑整理出来每一条都对应真实问题。5.1 随机数种子与结果可复现性蒙特卡洛的“随机”是把双刃剑。如果不设置随机数种子每次运行的曲线都会有细微差别提交报告或论文时连图都复现不出来导师问“为什么我跑出来和你不一样”就很被动。rng(2024)放在主程序开头一次性解决问题。但有个细节如果后续有人把随机数函数调用顺序改了固定种子下的随机序列也会变结果还是对不上。所以我做正式仿真时会把all_curves矩阵和参数表一起存成.mat文件连同绘图脚本一起归档。这样几个月后还能精确还原当时的图。5.2 跨天充电与边界截断这是最容易忽略的坑。如果一辆车晚上23:00开始充电需要充5小时第二天凌晨才结束按代码里的e min(s d - 1, params.slots)处理凌晨部分就丢了。结果就是凌晨0点到2点的负荷被低估峰谷形态失真。处理办法有两种。简单工况下把到家时间截断范围收紧到[8, 23]让绝大多数充电在当天结束误差可接受。正式研究场景我建议把负荷数组扩展成两天的长度也就是288个slot充电路径允许跨到第二天画图时只取前144个slot。实现也不复杂load长度改成2*params.slotse的min上限改成2*params.slots最后画图用load(1:params.slots)。5.3 Matlab中文注释乱码这个坑和算法无关但足够让人头疼。R2023a在中文Windows下打开早期版本保存的脚本中文注释经常全部变成乱码严重时还会影响代码运行。原因基本是文件编码不一致老版本默认可能是GBK新版本默认UTF-8。解决方法是在菜单“预设 → 常规 → 文本编码”里把编码设为UTF-8文件另存时也选UTF-8如果已经有乱码用记事本打开文件另存为UTF-8再重新打开。团队协作时统一编码比统一变量名还重要。5.4 性能优化循环改并行或向量化5000辆车200次模拟还能接受但如果把车辆数提到50000、模拟次数提到1000单层for循环就会变成性能瓶颈。两条路外层循环改用parfor并行池每个worker的随机流要单独处理否则并行之后固定种子会失效。内层单辆车累加改成向量化把每辆车的起始slot、结束slot、功率三个向量抽出来用accumarray或类似的统计函数按slot聚合。写起来没那么直观但速度提升非常明显。我在实际项目里一般先用for循环把模型跑通确认逻辑没错再根据计算量决定要不要优化。多数规划场景到不了必须优化的程度。5.5 后续可以怎么扩展蒙特卡洛框架最值钱的地方是“换规则方便”。想让它更贴近实际可以按需加这些模块加充电行为概率不是所有车到家都充按SOC阈值或电价激励决定插枪比例。分车型建模私家车、网约车、公交车分开设参数到家时间分布完全不同。考虑季节与温度冬季低温会让实际续航缩水日行驶里程到SOC的换算关系要调整。和配电网结合把生成的负荷曲线输入潮流计算工具直接看电压偏差和变压器负载率。用实测数据替换经验分布如果有某城市的出行调查数据或者充电桩运营数据直接用fitdist拟合分布比文献参数更贴合当地情况。这些扩展都不需要推翻现有代码只是在参数生成和单车规则上做文章。这也是我愿意把基础版本分享出来的原因——框架搭好后剩下的都是模型细节。
返回列表