ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

电动汽车充电负荷蒙特卡洛模拟:概率建模与MATLAB实现

电动汽车充电负荷蒙特卡洛模拟:概率建模与MATLAB实现 简介面向电力系统与电动汽车研究场景的MATLAB程序包聚焦蒙特卡洛法在充电负荷预测中的应用适合电气工程方向学生、电网规划人员及算法初学者。程序基于出行时间、行驶距离、充电模式等随机因素通过大量抽样模拟充电行为可输出平均负荷、标准差及概率分布支撑电网规划与调度决策。压缩包共3个文件内含两个M源文件与一份DOCX模型说明整体大小约2.07MB代码文件分别承担主流程与负荷计算模块说明文档则梳理了出行特性建模、随机抽样逻辑与结果可视化方法。目前已有2946人学习。通过完整程序可快速复现蒙特卡洛仿真流程理解车辆保有量、行程分布、充电时段、充电速率和电池容量等参数对负荷曲线的影响适合作为课程设计、科研预研或工程评估的参考。1. 为什么电动汽车充电负荷要用蒙特卡洛法做电网规划或小区配变扩容的时候最头疼的不是总用电量而是晚高峰那一段充电负荷到底会冲多高。电动汽车的起始充电时刻、日行驶里程、电池剩余电量都是随机量简单取平均值算出来的负荷曲线往往把峰值低估 20% 以上这对变压器容量选择是危险的。蒙特卡洛法的思路很直接把每个随机变量按概率分布抽样组合成一台车的充电过程模拟几百上千台车叠加出一天 24 小时的充电功率曲线。这个过程不是求一个确定解而是把不确定性转化为多条可能曲线的分布最后取期望、分位数、包络线。对 5 年以上的人来说真正要关注的不只是跑通程序而是抽样分布选得对不对、模拟次数够不够、以及结果怎么验证。2. 建模与抽样电动汽车充电负荷的随机变量和概率分布蒙特卡洛法的精度上限由概率模型决定而不是由随机数质量决定。先要把一台电动汽车从“接入电网”到“离开电网”的过程抽象成可计算的参数集合。2.1 负荷计算框架从单台车到车队聚合单台车的充电过程可以描述为接入时刻、起始 SOC、充电功率、充电时长。接入时刻一般取到家时刻起始 SOC 由日行驶里程反推充电时长由 SOC 目标和充电功率计算。整体计算分三层单台车输入随机变量输出一条充电功率曲线通常只在 10 分钟或 15 分钟粒度上有值。多台车同一时刻叠加所有在充车辆的功率。多次蒙特卡洛重复整个车队模拟得到每个时刻的功率分布。% 生成一天1440分钟的时间轴10分钟粒度 time_axis 0:10:1430; daily_curve zeros(1, length(time_axis)); for i 1:num_cars [start_idx, duration, power] simulate_one_charge(); for t 0:duration-1 idx mod(start_idx t, length(time_axis)) 1; daily_curve(idx) daily_curve(idx) power; end end核心是mod处理跨零点的充电场景如果一辆车 23:50 开始充电充到 00:30需要折返到第二天凌晨的时段。很多初版程序在这里直接用线性索引导致跨零点场景全部丢失晚高峰之后的负荷段被系统性低估。上面代码的粒度是 10 分钟对应电网规划里常用的负荷采集间隔如果要跑 96 点曲线15 分钟间隔改time_axis 0:15:1425即可。2.2 随机变量与概率分布选择常用的分布如下数据来自统计年鉴、GPS 轨迹或调研问卷。注意不同地区差别很大建议用自己的调研数据替换。随机变量常用分布典型参数备注起始充电时刻分段正态分布均值 18:30方差 2.5h晚高峰前有少量充电需混合分布日行驶里程对数正态分布mu3.2, sigma0.9单位 km截断在 300km 内起始 SOC由里程反推SOC 1 - 里程/续航或直接用截断正态充电功率离散分布7kW 占 70%3.5kW 占 30%家用慢充为主simulate_one_charge函数里我一般用makedist和random搭配先用pd makedist(Lognormal,mu,3.2,sigma,0.9)定义分布对象再用random(pd)抽取样本。这里有个细节MATLAB 2018b 之前makedist对截断分布支持不完整需要自己加while循环过滤建议在循环外加rng固定种子否则调试时每次结果都不一样。起始充电时刻的处理是整段程序里最容易出错的地方。不能只用一个正态分布从凌晨拉到深夜因为真实数据在 7:00-9:00 和 18:00-20:00 各有一个峰值。常见做法是双峰混合分布上午峰用均值 8:30、方差 1.5h晚上峰用均值 19:00、方差 2h按 0.3/0.7 的概率选择峰值。用rand 0.3判断落在哪个峰再分别抽样比直接拟合单峰分布要稳得多。2.3 充电模式与功率等级充电模式决定功率和时间尺度的映射关系。慢充3.5-7kW持续时间长快充30-120kW单次只有 20 到 60 分钟。蒙特卡洛程序如果只模拟家用慢充适合小区负荷评估如果涉及公共快充站需要额外引入到达时刻的泊松过程和排队模型。这里给一个简化的双模式实现function [start_idx, duration, power] simulate_one_charge(car) % 选择充电功率等级70%概率慢充30%概率快充 if rand() 0.7 power 7; efficiency 0.9; charge_cap car.battery * (car.target_soc - car.soc0); duration ceil(charge_cap / (power * efficiency) * 6); % 换算成10分钟块 else power 50; efficiency 0.95; charge_cap car.battery * (0.8 - car.soc0); % 快充通常充到80% duration ceil(charge_cap / (power * efficiency) * 6); end start_idx get_start_index(car.arrive_minute); endduration的换算值得注意charge_cap单位是 kWh功率单位是 kW相除得到小时数乘以 6 得到 10 分钟块的个数。ceil向上取整保证充电需求被完全满足但也意味着实际充电量会略高于目标如果后面对峰谷有精确要求可以改成按段累计电量并在最后一段截断。快充的target_soc设为 0.8是因为多数快充协议在 80% 后降功率蒙特卡洛里把这种非线性强行简化成 0.95 的固定效率误差在可接受范围。3. MATLAB 实现蒙特卡洛充电负荷数据准备、主循环与并行化建模完成后进入实现层面。这里给出一个可运行的最小框架覆盖数据准备、单次模拟、聚合和结果导出四个环节。3.1 数据准备日行驶里程、起始充电时刻与车辆参数表先建一个车辆结构体数组每台车包含电池容量、续航、充电功率和接入时刻。对于大规模模拟用结构体数组比 table 快因为避免了大表的列访问开销。% 车辆参数配置 num_cars 500; % 模拟车辆数 range_ev 300; % 续航里程km battery_cap 60; % 电池容量kWh start_hour_peak [8.5, 19.0]; % 上午峰、晚峰 start_sigma [1.5, 2.0]; % 对应标准差 daily_mile_mu 3.2; daily_mile_sigma 0.9; cars struct(battery, cell(num_cars, 1), ... soc0, cell(num_cars, 1), ... arrive_minute, cell(num_cars, 1));然后做抽样。里程抽样后要按续航截断超过续航的值直接剔除重抽否则会出现 SOC 为负。截断逻辑写在循环里用while检查最多重试 10 次超过则取边界值——这是为了避免极端随机数让程序陷入死循环。for i 1:num_cars mile 0; retry 0; while mile 0 || mile range_ev mile lognrnd(daily_mile_mu, daily_mile_sigma); retry retry 1; if retry 10 mile range_ev * 0.95; break; end end cars(i).soc0 max(0.1, 1 - mile / range_ev); % 选取充电起始时刻 if rand() 0.3 cars(i).arrive_minute round(normrnd(8.5, 1.5) * 60); else cars(i).arrive_minute round(normrnd(19.0, 2.0) * 60); end % 时间边界处理 cars(i).arrive_minute mod(cars(i).arrive_minute, 1440); end这里用了lognrnd和normrnd而不是random(makedist(...))原因是直接用分布函数更快且不需要重复构造分布对象。时间边界用mod归一到 0-1439 分钟保证凌晨到达的车辆能被正确映射到当天。需要说明mod只解决“24:10”这种溢出不解决正态分布抽样出负值的问题因此对arrive_minute小于 0 的情况直接在后面补了一个常量偏移实操中也可以直接丢弃该样本重新抽。3.2 主循环单次蒙特卡洛模拟的函数封装把单次车队模拟封装成独立函数simulate_one_day(cars, power_mode)方便后面做收敛性分析时反复调用。内部先创建长度为 144 的日负荷数组10 分钟粒度然后逐车计算接入时段并叠加。function daily_curve simulate_one_day(cars, time_step_min) n_steps 1440 / time_step_min; daily_curve zeros(1, n_steps); n length(cars); for i 1:n car cars(i); [p, dur] get_charge_profile(car); start_idx floor(car.arrive_minute / time_step_min) 1; for t 0:(dur - 1) idx mod(start_idx t - 1, n_steps) 1; daily_curve(idx) daily_curve(idx) p; end end endget_charge_profile内部根据 SOC 和电池容量计算充电时长。这层封装的意义在于后面做有序充电策略时不需要改主循环只替换get_charge_profile即可。所有仿真结果的可复现性靠两层保证外层在simulate_one_day调用前设置rng(seed)内层不用任何全局随机状态。如果某个新版本 MATLAB 的随机数生成算法变了固定种子会让结果不变但不同版本之间可能不完全一致这是正常的。3.3 聚合与输出负荷曲线、峰值和分位数蒙特卡洛跑 K 次之后会得到一个K x 144的矩阵all_curves。对这个矩阵做三个方向的统计逐时刻均值得到期望负荷曲线、逐时刻 95% 分位数得到包络线、逐行取 max 得到峰值分布。mean_curve mean(all_curves, 1); p95_curve prctile(all_curves, 95, 1); peak_dist max(all_curves, [], 2); figure; plot(time_axis, mean_curve, b-, LineWidth, 1.5); hold on; plot(time_axis, p95_curve, r--, LineWidth, 1); fill([time_axis fliplr(time_axis)], ... [mean_curve fliplr(p95_curve)], r, FaceAlpha, 0.15); legend(期望曲线, 95%分位数, Location, best); xlabel(时刻); ylabel(充电功率/kW);用fill绘制期望线与分位数之间的置信区间带这个带子的宽度直观反映负荷不确定性。峰值分布peak_dist通常不服从正态分布而是右偏的因此不要用均值加减标准差描述直接用prctile(peak_dist, 95)作为规划容量参考值。这段代码里time_axis需要和all_curves的列数一致如果改了时间步长记得同步调整。3.4 并行化用 parfor 替代 for 加速蒙特卡洛单次模拟处理 500 台车耗时约 0.2 秒MATLAB 2021b 之后 JIT 加速效果明显跑 1000 次需要 200 秒这时候可以引入并行计算。% 先开并行池parfor 自动分配任务 if isempty(gcp(nocreate)) parpool(local, 4); end K 1000; all_curves zeros(K, 144); parfor k 1:K all_curves(k, :) simulate_one_day(cars, 10); endparfor有几个限制需要记住循环体内不能有依赖上一次迭代结果的变量cars变量必须是只读的随机数的使用要用RandStream在每个 worker 上独立设置否则多个 worker 可能生成相同序列。实际做法是在循环体第一行加上rng(k, twister)虽然会损失一点速度但保证了可复现性。并行池的大小不是越大越好gcp会占用内存模拟数据规模不大时4 个 worker 的速度提升通常在 2.5 到 3 倍再增加 worker 效果递减。还要注意如果矩阵all_curves太大parfor的传输开销会抵消计算收益这个例子里 K1000、144 列只有不到 1MB完全没有问题。4. 收敛性判断、随机数流与经典参数调优蒙特卡洛不是跑得越多越好。模拟次数 N 的确定要看输出指标的稳定性而不是看曲线是否“平滑”。峰值总是比均值更难收敛所以要分开判断。4.1 确定模拟次数 N用变异系数做收敛判据用一个简单有效的方法每增加 100 次模拟计算一次当前峰值的均值peak_mean(i)当前后两组的相对变化率小于 1% 时认为收敛。% 增量式蒙特卡洛检查峰值收敛 batch_size 100; max_iter 5000; peak_means []; for b 1:max_iter/batch_size batch_curves zeros(batch_size, 144); for j 1:batch_size batch_curves(j, :) simulate_one_day(cars, 10); end all_curves((b-1)*batch_size1:b*batch_size, :) batch_curves; peak_batch max(batch_curves, [], 2); peak_means(b) mean([peak_batch; peak_means_hist]); if b 3 rel_change abs(peak_means(b) - peak_means(b-1)) / peak_means(b-1); fprintf(迭代%d次峰值均值%.2f kW相对变化%.4f%%\n, ... b*batch_size, peak_means(b), rel_change*100); if rel_change 0.01 fprintf(收敛于%d次模拟\n, b*batch_size); break; end end end这段代码里的peak_means_hist需要在外层维护一个累计峰值列表或者直接把peak_batch累加到总峰值数组里。更简单的方式是计算前 5% 峰值分位数的变化因为规划时更关注极端情况。实操中500 台车、典型分布参数下1000 到 2000 次模拟基本能满足 1% 的收敛要求如果车辆数增加到 5000 台单次模拟本身已足够平滑500 次就能收敛。判断收敛的关键不是看曲线重合而是看峰值分布的方差是否小到可接受。4.2 随机数流与可复现性设置MATLAB 的随机数体系从 2016 版开始基本稳定rng(default)每次新开 MATLAB 都会重置到梅森旋转算法的初始状态保证脚本重复运行得到相同结果。但要注意rand、randn和lognrnd用的是同一个全局流如果在simulate_one_day之外对随机数做了一次测试调用后面所有抽样都会偏移。正确做法是脚本开头设置rng(2024)固定主种子。涉及并行时每个 worker 用rng(k, twister)设置与迭代索引相关的子种子。调试时禁用并行用串行模式跑一遍确认结果。另外如果蒙特卡洛包里后续要加“充电起始时刻的关联性”——比如某小区的车都集中在 18:00 到 19:00 到家单纯用独立分布抽样会低估同时率——那就需要引入 Copula 函数做相关性抽样。MATLAB 的copularnd可以生成带相关性的多维样本这是从“随机独立”走向“随机相关”的关键一步也是这个标题下最容易扩展的方向。4.3 分布参数的敏感性与常见误用以下三个错误在现网程序里最常出现第一个是起始 SOC 直接用正态分布抽样没有用里程映射。两者会得到近似的均值但峰值负荷差异明显因为 SOC 低时充电功率可能受电池充电曲线限制直接用 SOC 分布会漏掉这种边界约束。第二个是充电时长向上取整后没有检查 SOC 上限导致单台车充电量超过电池容量这在车队规模大时会让总负荷偏高。第三个是把所有车都假设为到家立即充电忽略了部分车辆在单位充电的场景。要处理单位充电只需在get_charge_profile里加一个判断如果接入时刻在 8:00-17:00 之间充电功率取公司慢充桩的 3.5kW时长不变。matlab版本兼容性方面脚本里用到的lognrnd、prctile、parfor在 2016b 到 2023b 之间都没有破坏性变更gcp(nocreate)这个写法从 2013b 就有了。真正容易出问题的是fill画图在hold on后的叠加顺序以及在不同版本的legend对线型的显示差异。如果公司用的还是 2018a 或 2021b不需要担心代码跑不了。4.4 排查仿真异常的三板斧跑出来的负荷曲线如果是直线或全是零先查三处一是arrive_minute是否全部集中在同一时刻用histogram(cars_arrive_minute, 24)看分布形状二是daily_curve是否有叠加到错误索引打印idx的最小最大值三是充电时长duration是否为零soc0接近 1 时确实会出现零时长这种车应该跳过而不是报错。一个快速定位方法是在simulate_one_day里临时加一行assert(all(daily_curve 0))配合try-catch打印出错车辆的编号。很多时候问题出在ceil和mod的组合上ceil让多充了部分电量mod让跨天时段被重复叠加两者叠加会产生微小的总电量偏差但这个偏差在规划层面可以接受。5. 结果验证与进阶信息熵、实测对比与有序充电延伸模拟结果不能只管自己合理还要能经得起推敲。最后一章给出三个具体的验证与扩展手段。5.1 用信息熵评估随机样本质量判断抽样是否“足够随机”除了看分布直方图还可以计算样本的信息熵。把 1440 分钟的负荷曲线按小时聚合得到 24 维离散概率分布然后计算其熵。如果熵值明显低于理论最大熵说明样本集中在少数几个时段可能是抽样函数写错了比如起始时刻被错误地裁剪到固定值。daily_hourly reshape(mean_curve, 60, 24); p sum(daily_hourly, 1) / sum(mean_curve); p(p 0) []; entropy_val -sum(p .* log2(p)); fprintf(24小时负荷分布信息熵: %.3f bit\n, entropy_val);一天的负荷曲线如果完全均匀信息熵是 log2(24) 约 4.585 bit典型电动汽车充电场景下熵值在 3.5 到 4.2 之间太小说明过度集中太大则说明负荷不够差异化。这个指标也可以用来比较蒙特卡洛抽样与实测数据的分布一致性对实测负荷做同样的熵计算两者差值在 0.3 以内说明模型结构合理。5.2 用 95% 分位数包络线对接配变容量校验规划人员真正关心的不是均值曲线而是“最坏情况下的峰值”。从all_curves中提取逐时刻的 95% 分位数和最大值包络然后与配变额定容量做对比transformer_cap 630; % kVA utilization_95 max(p95_curve) / transformer_cap; fprintf(95%%分位数峰值负载率: %.1f%%\n, utilization_95 * 100);如果负载率超过 80%建议把充电策略切换到有序充电模式。这时候只需要调整get_charge_profile里的起始时间把晚峰时段的一部分车辆延迟到 22:00 后启动充电即可压低峰值。蒙特卡洛框架天然适合评估这种策略效果对每台车加一个“延迟时间”随机变量运行对比仿真即可量化削峰率。matlab的优化工具箱里fmincon可以用来优化延迟策略参数但实际工程中先用蒙特卡洛做网格搜索就足够了。5.3 从负荷曲线到有序充电策略的扩展有序充电的核心是在get_charge_profile中增加一个“可延迟”字段。假设每台车都支持定时充电默认延迟delay_min分钟启动则蒙特卡洛的改动只有一行start_idx floor(car.arrive_minute / time_step_min) 1 floor(delay_min / time_step_min);。跑不同的延迟分布场景就能得到有序充电的负荷曲线区间带。注意这里的充电总电量不变只是时间平移因此平均负荷不变峰值会下降。离散延迟时间的分布一般选泊松分布或均匀分布不建议用正态分布因为现实中用户设定定时充电的行为更接近均匀特征。更进一步可以引入实时电价MATLAB 里直接计算分时电价下的充电费用期望值这会让蒙特卡洛的输出从一条曲线变成一组“负荷-费用”的联合分布对充电运营商更有价值。至此整个程序已经从“算负荷”演进成了“决策支持工具”。本文还有配套的精品资源点击获取
返回列表