做电动汽车充电负荷仿真的活儿看着简单,里头的门道其实不少。很多人手里拿到一片区域的EV规模数据后,第一反应是用“平均功率×车辆数”直接画一条直线,但真实充电行为完全是随机事件——几点插枪、充多久、用快充还是慢充,每辆车都不一样。蒙特卡洛模拟就是处理这种随机性的标准工具,通过海量抽样把不确定性转化成一条带统计意义的24小时负荷曲线。这篇文章就从零讲清楚我用Matlab做EV充电负荷仿真的完整过程,重点是按充电频次把用户分成一充、二充、三充三类分别建模,再做蒙特卡洛抽样,最终输出负荷曲线。做配电网规划、充电桩容量设计和微网调度的人,以及准备拿这类仿真做课程设计或论文的同学,都可以直接参考这套流程。
1. 为什么用蒙特卡洛模拟电动汽车充电负荷
1.1 一充、二充、三充三类用户划分的价值
先想一个问题:一个小区里500辆电动汽车,每辆车充电行为都一样吗?显然不是。有人是普通上班族,每天通勤20公里,晚上回家插上慢充,第二天满电出门,这是一充;有人通勤距离远,中午趁午休在单位附近快充半小时补一次电,晚上回家再充一次,这是二充;还有跑网约车、出租或者做物流配送的,一天要跑两三百公里,早上出车、下午收车、晚上回家,路上得补好几次电,这是三充。
如果不做分类,把所有车混在一起取平均值,最后得到的负荷曲线会非常“平”,中午和晚上的尖峰都会被抹掉,峰谷特征完全失真。而把用户按充电频次分开建模,就能清晰地看到不同用户群体对电网负荷的叠加效果——晚上的一充用户形成夜间宽峰,午间的二充、三充用户形成白天的窄尖峰。这对规划充电桩很有用:如果一个区域里网约车比例高,白天快充需求就大,光靠晚间慢充曲线来设计容量肯定会出偏差。
有人会问,三类比例怎么定?这没有统一答案,需要结合所在城市的出行调查数据、车辆保有结构、网约车渗透率来标定。一般当下大部分城市私家车占绝对主体,一充比例在50%上下,二充在30%左右,三充约20%。这个比例后期可以按实测数据调整,我做的代码里把比例做成了参数,方便直接改。
1.2 仿真整体流程与角色分工
整个仿真的技术路线大致分五步:设置基础参数、抽取每一辆车的充电事件、按时间轴叠加负荷、重复蒙特卡洛循环、统计输出。
第一步的参数区包括仿真车辆数、蒙特卡洛次数、电池容量、慢充功率、快充功率、三类用户占比等。第二步是核心,对每辆车先按概率判定它属于一充、二充还是三充,再根据类型抽取每次充电的起始时间和起始SOC,结合充电功率算出充电时长,形成一个完整的“充电事件”。第三步把每辆车的充电事件按分钟时间轴累加,相当于把所有EV的充电功率叠加到同一条曲线上。第四步重复第二步和第三步几百次,因为每次抽样结果都有随机波动,单次仿真只能算是“一次可能的场景”,跑几百次取平均才逼近统计期望。最后一步对几百条结果曲线做均值、标准差和95%置信区间计算,画图输出。
这五步里,蒙特卡洛重复次数不是越高越好,太高了计算耗时成倍增加。实测下来,500辆车配500次仿真,普通笔记本大概跑几十秒,精度已经够用;如果车辆数增加到几千辆,蒙特卡洛次数可以降到200次,通过对曲线平滑处理后结果依然很稳定。
2. 关键概率分布与仿真参数建模
2.1 起始充电时间与充电模式
起始充电时间是最影响负荷曲线形状的随机变量。一充用户普遍的规律是下班到家后插枪,集中在傍晚6点到晚上10点,峰值大约在晚上7点半到8点。这个分布可以用截断正态分布描述,均值取19.5,标准差取1.2,再截断到17点到23点之间,避免抽到深夜或凌晨这种不合理的时间。
二充用户分两段:第一段在午间11点到下午2点,峰值在12点半左右,这个时段正好是午休时间,车停着不用,抽空补一次电是合理行为;第二段跟一充用户的晚高峰重叠,也在19点半附近。三充用户更复杂一点,早上8点半、下午1点半、晚上7点半各有一段。早上的充电是因为网约车司机出车前或接早高峰前要把电量补到90%以上,下午是收车前的最后补电,晚上则跟私家车一起扎堆抢桩。
充电模式上,一充用户几乎全用慢充,也就是家用交流桩,单相7kW;白天补电的二充、三充用户更倾向快充,因为时间贵,快充桩60kW或120kW是主流。行业统计里,商用车的白天快充占比远高于私家车。所以我建模时把充电类型和充电功率绑定:一充默认慢充;二充第一次快充、第二次慢充;三充全部快充。
2.2 行驶里程、SOC与充电时长推算
电动汽车到达充电桩时的剩余电量(SOC)决定了要充多久。比较严谨的做法是用日行驶里程推算出发时的SOC:假设满电出发,每百公里耗电约15kWh,电池容量50kWh,那么跑完50公里后,SOC约等于1减掉0.15乘50再除以50,也就是0.85。日行驶里程本身服从对数正态分布,均值和方差需要根据出行统计数据来设。
我在实际代码里为了直观和稳定,采用了另一种更常用的做法——直接对起始SOC做均匀分布抽样。一充用户出发时SOC通常在0.4到0.8之间,二充第一次在0.4到0.7、第二次在0.3到0.6之间,三充因为补电频繁,两次充电之间电量消耗不算太大,SOC集中在0.3到0.6。这样做的优点是代码简单、结果容易复现,数学上跟里程推算法的差别也不算大,因为充电负荷的核心特征是受起始SOC分布影响,而不是具体SOC的物理推导过程。
充电时长的计算方法是:需要充入的电量等于电池容量乘以(1减去起始SOC),再除以充电功率。举个例子,一辆电池容量50kWh的车,起始SOC是0.5,需要充入25kWh,用7kW慢充,时长就是25除以7约3.57小时,大概3小时35分钟。这个时间会自然反映在负荷曲线上的宽峰形态上。
2.3 三类用户参数配置表与调整建议
我把整个仿真涉及的参数汇总成一张配置表,方便直接复制到代码里按需修改。要注意的是,这些参数的数值不是拍脑袋定的,每一条都对应实际场景特征。
| 充电类型 | 概率占比 | 充电时段分布 | 充电功率 | 起始SOC范围 | 典型用户 |
|---|---|---|---|---|---|
| 一充 | 50% | 晚19:30(σ=1.2h),截断至17~23点 | 7kW慢充 | 0.4~0.8 | 普通上班族 |
| 二充 | 30% | 午12:30(σ=1h)+ 晚19:30(σ=1.2h) | 50kW快充+7kW慢充 | 0.3~0.7 | 长距离通勤私家车 |
| 三充 | 20% | 早8:30 + 午13:30 + 晚19:30 | 50kW快充×3 | 0.3~0.6 | 网约车、出租车、物流车 |
调整建议就一条原则:先把当地统计数据和实际调研数据找出来,再反过来改表里的值。比如你知道某城市的充电桩以慢充为主、快充很少,就把快充功率那一列的占比调低;如果你研究的是专用充电站——路上全是网约车,那一充的比例就明显不合理,应该把三充比例抬到60%以上。仿真的魅力恰恰在于参数可以灵活改,曲线跟着变,模型始终为用户的实际场景服务。
3. Matlab完整实现:主程序与核心代码
3.1 环境初始化和参数区
先建一个脚本文件,名字随意,比如ev_mcs_load.m,在头部做好环境清理和随机种子设置。随机种子很重要,不设的话每次跑出来的曲线都不一样,没法对比参数修改前后的差异。rng(0)表示固定随机数生成器的初始状态,这样别人复现你的代码能得到完全相同的结果。
%% 环境初始化 clear; clc; rng(0); %% 基础参数设置 n_ev = 800; % 仿真电动汽车数量 n_mc = 500; % 蒙特卡洛循环次数 n_minutes = 24 * 60; % 全天分钟数 1440 % 三类用户占比:一充、二充、三充 p_type = [0.5, 0.3, 0.2]; % 电池与充电参数 battery_cap = 50; % 电池容量,单位 kWh p_home = 7; % 家用慢充功率,单位 kW p_fast = 50; % 公共快充功率,单位 kW % 结果存储矩阵 load_curve_all = zeros(n_mc, n_minutes); % 每行是一次蒙特卡洛仿真的完整负荷曲线车辆数和蒙特卡洛次数是影响计算时间的两个关键旋钮。800辆车、500次循环是我调试出来的一个平衡点:曲线已经很平滑,单次运行时间在一分钟左右,属于“可以坐在椅子上等出图”的舒适区间。如果只是课程演示,把n_ev降到300也能出图,但曲线尾部的毛刺会明显变多。
3.2 蒙特卡洛主循环与充电事件生成
核心循环分成两层:外层循环是蒙特卡洛次数,内层循环是每一辆EV。每一次内层循环都对一辆车做一次完整的“充电行为模拟”:先判定充电类型,再根据类型抽取充电时段,然后逐段计算充电时长并叠加负荷。
%% 蒙特卡洛主循环 for mc = 1:n_mc load_curve = zeros(1, n_minutes); % 本次仿真的负荷曲线 for i = 1:n_ev % 1) 判定该车属于哪一种充电类型 r_type = rand; if r_type <= p_type(1) ctype = 1; % 一充 elseif r_type <= sum(p_type(1:2)) ctype = 2; % 二充 else ctype = 3; % 三充 end % 2) 按充电类型抽取充电起始时刻 switch ctype case 1 n_charge = 1; t_start = normrnd(19.5, 1.2); t_start = max(17, min(23, t_start)); % 截断到合理区间 is_fast = false; % 一充用慢充 case 2 n_charge = 2; t_start1 = max(11, min(14, normrnd(12.5, 1))); t_start2 = max(17, min(23, normrnd(19.5, 1.2))); t_start = [t_start1, t_start2]; is_fast = [true, false]; % 午间快充,晚间慢充 case 3 n_charge = 3; t_start1 = max(7, min(10, normrnd(8.5, 0.8))); t_start2 = max(12, min(15, normrnd(13.5, 0.8))); t_start3 = max(18, min(22, normrnd(19.5, 1.0))); t_start = [t_start1, t_start2, t_start3]; is_fast = [true, true, true]; % 白天多次快速补电 end % 3) 遍历每一段充电事件,计算充电功率并叠加 for j = 1:n_charge % 抽取起始SOC if ctype == 1 soc_start = 0.4 + 0.4 * rand; % 0.4 ~ 0.8 elseif ctype == 2 if j == 1 soc_start = 0.4 + 0.3 * rand; % 0.4 ~ 0.7 else soc_start = 0.3 + 0.3 * rand; % 0.3 ~ 0.6 end else soc_start = 0.3 + 0.3 * rand; % 0.3 ~ 0.6 end % 按充电类型选择功率 if is_fast(j) p_ch = p_fast; else p_ch = p_home; end % 计算充电时长,并限制不超过6小时 t_dur = (1 - soc_start) * battery_cap / p_ch; t_dur = min(t_dur, 6); % 将充电事件叠加到负荷曲线上 idx_start = round(t_start(j) * 60) + 1; idx_end = idx_start + round(t_dur * 60) - 1; idx_vec = idx_start:idx_end; idx_vec = mod(idx_vec - 1, n_minutes) + 1; % 超过24点回绕到次日凌晨 load_curve(idx_vec) = load_curve(idx_vec) + p_ch; end end load_curve_all(mc, :) = load_curve; end逐段解释一下几个关键设计。第一,一充t_start是抽取完再用max和min做截断,而不是先截断再抽样,这保证落在17点之前或23点之后的极端样本被修正到边界,不会出现半夜两点插枪的荒唐场景。第二,SOC范围的选取用第三行0.4 + 0.4 * rand生成0.4到0.8区间,这个写法比rand直接乘上界要直观,也方便后期调整区间。第三,充电时长上限取6小时是为了模拟“充满即走”的用车习惯,虽然现实里很多人整晚插着枪不拔,但负荷贡献只到充满那一刻为止,后面只是待机状态,6小时这个上限对大多数家用慢充场景已经足够了。
3.3 负荷曲线叠加与跨日处理细节
跨日处理是这段代码里最容易写错的地方。一辆车在晚上11点开始充电,充了4个小时,到凌晨3点结束,这在24小时时间轴上就跨过了0点边界。如果直接用load_curve(idx_start:idx_end) = ...,idx_end超过1440就会弹出索引越界的报错。
我用的方法是让索引向量整体对1440取模,mod(idx_vec - 1, n_minutes) + 1这条式子的效果是把超过1440的索引映射到起点,比如1445变成5,对应凌晨0点05分。这里有三个前提:第一,充电时长必然小于24小时,所以取模不会出现同一个索引被重复叠加两次的情况;第二,叠加方式是直接加功率值,不涉及累减或覆盖,所以索引映射是安全的;第三,时间分辨率是分钟,取整误差最大不超过半分钟,对整体曲线几乎没有影响。
还有一个细节值得注意:rand和normrnd的调用非常频繁,在800辆车、500次蒙特卡洛的规模下,总共要生成上百万个随机数。Matlab处理这个量级没有问题,但如果把车辆数提高到5000以上,建议先把所有随机数一次性生成成矩阵,再按列取用,这样能减少随机数生成器调用的开销,让运行速度快三分之一左右。
3.4 可视化与置信区间输出
跑完循环后,load_curve_all矩阵的每一行就是一条全天1440分钟的负荷曲线。接下来对每一列(也就是每一个分钟点)求均值和标准差,再画图。标准差用来画95%置信区间带,能直观看到哪些时段的不确定性大。
%% 统计结果 mean_load = mean(load_curve_all, 1) / n_ev; % 平均每辆EV的充电负荷,kW/辆 std_load = std(load_curve_all, 0, 1) / n_ev; %% 画图 t_hour = (0:n_minutes - 1) / 60; figure; hold on; fill([t_hour, fliplr(t_hour)], ... [mean_load + 1.96 * std_load, fliplr(mean_load - 1.96 * std_load)], ... 'b', 'FaceAlpha', 0.2, 'EdgeColor', 'none'); plot(t_hour, mean_load, 'b-', 'LineWidth', 1.5); grid on; xlabel('时间 (h)'); ylabel('平均负荷 (kW/辆)'); title('电动汽车蒙特卡洛仿真负荷曲线(500次抽样)'); legend('95%置信区间', '平均负荷'); xlim([0 24]);除以n_ev的原因是把总负荷折算成“单辆EV平均负荷”,这样不依赖具体的车辆数,论文里也习惯这么表达。如果你想看整个区域的原始总负荷,不除以n_ev就行,曲线形状完全一样,只是纵轴数值放大。
实际跑完的图形上,你能清晰看到三条脊线:午间11点半到下午2点半之间有一个中等的凸起,那是二充和三充的快充贡献;傍晚5点到晚上9点之间是最大的高峰,一充和二充、三充的晚段叠加在一起;凌晨1点到5点还有一条长尾,是晚回家的车还在慢充。这三段形态跟真实电网记录的EV充电负荷曲线非常接近。
4. 仿真结果分析与曲线形态解读
4.1 三类用户负荷曲线特征对比
为了看清每一类用户的“长相”,可以把代码稍作修改,只保留某一类车辆参与仿真,其余类型跳过,分别跑三次。我建议你亲手做一下这个对比实验,因为它能让你直观理解为什么要分类。
单独跑一充用户时,曲线形态很简单:下午基本是零,傍晚6点左右开始爬升,8点左右到达峰值约0.28kW/辆,然后缓慢下降,凌晨4点归零。单台车峰值为7kW,折算到单辆平均后压低是因为不是所有车都在同一时刻充电,这正是蒙特卡洛模拟的价值——把满负荷的7kW“摊开”成一条与车辆规模无关的归一化曲线。
单独跑二充用户时,午间出现一个明显的窄峰,峰值大概出现在12点半到1点,数值在0.1kW/辆附近,宽度只有两三个小时。这是因为50kW快充的充电时长短,SOC从0.5充到0.8一般只要20分钟左右,短时间高功率叠加,尖峰又高又窄。晚上的第二段慢充与一充形态类似,但峰值略低,因为这类用户晚上充电时SOC没一充用户那么低,充电时长稍短。
单独跑三充用户时,曲线变得很“碎”:早上9点前后一个小峰,下午2点前后另一个峰,晚上8点前后主峰。每个峰的宽度都窄,因为全是快充。三充用户的总充电次数多,但每次充电时间极短,总用电量反而可能不比一充用户高多少。
4.2 峰谷特性与配电网影响判断
把三类放在一起看全场景曲线,最需要注意的是峰谷差。负荷曲线的最大值和最小值之差,直接决定变压器容量和配电线路的裕度设计。假设某小区有1000辆EV,按我的参数跑出来的结果,全网总负荷峰值大概在250kW到300kW之间,出现在晚上7点半到8点半,而凌晨4点的低谷可能只有20kW。这意味着配电变压器需要在晚高峰多留出近300kW的容量余量,这对一个原有几百千瓦生活用电的小区来说压力不小。
再看午间快充峰的影响:800辆车里有30%是二充、20%是三充,快充车辆总数约400辆,即使只有一半在午间充电,叠加起来也有200kW的瞬时负荷。办公楼、商业区配电网白天本身就处于用电高峰,这个叠加效应容易导致线路过载,所以在做充电桩规划时,一定要分场景分析午间快充峰的影响,不能只看晚间慢充这一个大峰。
4.3 蒙特卡洛收敛性验证方法
蒙特卡洛模拟的口碑建立在“结果稳定”这四个字上,但很多人跑完一次就直接出图,心里没底。我建议每次仿真结束后顺手做一个收敛性检验:把load_curve_all按行累加取均值,观察结果是否随仿真次数增加趋于平稳。
%% 收敛性分析:观察晚高峰负荷在不同MC次数下的累计均值 cum_mean = cumsum(load_curve_all, 1) ./ (1:n_mc)'; figure; plot(1:n_mc, cum_mean(:, 780), 'k-', 'LineWidth', 1.5); xlabel('蒙特卡洛次数'); ylabel('19:00 负荷累计均值 (kW)'); title('蒙特卡洛收敛性曲线'); grid on;我用的是晚上7点(第780分钟)这个关键时间点,因为它是晚高峰的中心。理论上,随着仿真次数增加,累计均值会从波动逐渐收敛到一条水平线。实测数据里,前50次波动幅度可能达到5%以上,到200次之后波动通常小于1%,500次后曲线基本平直。如果你的收敛曲线在200次之后还在明显震荡,往往是车辆数选得不够——n_ev偏小时,单次仿真内部的随机性很大,需要更多次蒙特卡洛循环来补偿。一个快速判断标准:晚高峰均值波动小于0.01kW/辆时,结果就可以放心使用了。
5. 常见问题排查与调参经验
5.1 结果异常问题速查表
实际跑仿真时遇到的问题我基本都撞过一遍,整理成一张速查表放在下面。如果你跑出来曲线形状奇怪,优先对照这张表找原因。
| 异常现象 | 可能原因 | 解决方案 |
|---|---|---|
| 曲线毛刺多、锯齿严重 | n_ev太小或n_mc太少 | 提高n_ev到500以上,或增加n_mc到300次 |
| 午间出现异常尖锐的峰 | 快充功率设定不合理 | 检查p_fast是否过高,快充桩实际功率往往低于额定值 |
| 夜间曲线掉不到零 | 充电时长上限没限制或SOC范围过宽 | 检查t_dur = min(t_dur, 6)是否被删除 |
| 峰值出现在凌晨而非晚高峰 | 起始充电时间截断区间错位 | 检查max(17, min(23, ...))的截断范围是否写反 |
| 报错“索引超出矩阵维度” | 未做跨日映射,idx_end超过1440 | 确认用了mod(idx_vec - 1, n_minutes) + 1 |
| 每次运行结果差异极大 | 没有固定随机种子 | 脚本开头加rng(0) |
| 负荷峰值超过车辆×功率 | 同一辆车的充电事件被重复叠加 | 检查SOC循环里是否误把慢充和快充都算了一遍 |
| 午间曲线完全消失 | p_type里二充、三充比例设成了0 | 检查参数区p_type赋值语句 |
5.2 参数调整的避坑心得
第一个坑是充电功率的取值。很多人为了省事把所有充电都设成7kW或所有都设成50kW,结果曲线要么太平、完全没有快充尖峰,要么尖峰吓人、全天负荷都在高位。现实中充电功率不是恒定值,快充桩在电池SOC超过80%后功率会大幅下降,我建模用的50kW是“平均有效功率”的近似,适用于SOC在0.3到0.8区间的快速补电。如果一定要更准确,可以把快充事件拆成两段:前20分钟按满功率,后20分钟按20kW降功率充电。这段逻辑加进去后,曲线会变得更圆润,不过对于宏观负荷规划来说,简化的平均功率模型已经足够。
第二个坑是SOC范围和充电时长上限。有次我把一充用户的SOC范围改成0.3到0.95,结果夜间曲线从晚上7点一直拖到早上7点还降不下来,原因是有大量低SOC车辆要充6小时以上。实际生活里很少有人把电池用到5%以下才充电,SOC下限设到0.2已经算极端情况了,0.3到0.4的下限更贴近真实充电习惯。
第三个坑是三类用户比例和充电次数绑定得太紧。实际场景中,一充用户里也有偶尔白天补电的,二充用户里也有用慢充的,三个类型之间的边界是模糊的。如果想更精细化,可以用mixing模型:先按比例抽取类型,再给每种类型一个“偏离概率”,比如一充用户有5%的概率额外增加一次白天补电。我的代码里没有做这层处理,主要是为了让逻辑清晰、方便教学;真要用于商业项目,建议加入模糊转移概率,让车辆在不同类型之间可以“跨界”。
最后再分享一个经验:参数修改完之后,前后两次仿真结果要用同一个随机种子来对比,否则你无法判断曲线的变化到底是参数引起的,还是纯随机波动造成的。我自己的习惯是,每调一次参数就把随机种子固定,并保存一份当时的曲线图像文件,方便后面复盘。这个方法虽然简单,但能帮你省掉很多“为什么曲线和上次不一样”的困惑。