简介:面向电动汽车与电网互动(V2G)研究场景,本程序以MATLAB实现基于粒子群算法的充放电优化模型,适合电气工程、新能源方向的学生与工程师用于仿真分析与算法验证。模型在保证用户出行需求的前提下,根据光伏出力与工作区负荷的偏差制定动态分时电价,引导电动汽车尽可能多地消纳商场基础负荷剩余的光伏电量,从而降低配电网峰谷差、提升电网稳定性,同时兼顾用户充放电满意度。压缩包内共8个文件,包括6个MATLAB脚本(分别承担主程序、目标函数、粒子群优化、参数初始化、SOC约束及负荷计算等功能)、1份PDF建模说明和1份Excel基础数据,大小仅320KB,结构清晰、便于对照学习。目前已有158人学习/下载。程序以配电网负荷方差最小为目标函数,融合常规负荷、光伏出力与电动汽车充放电电量,充分体现电动汽车的源-荷二重性,并附带详细的建模说明文档,既适合初学者理解V2G调度与粒子群算法原理,也便于研究者在此基础上开展改进与扩展实验。
1. 为什么V2G模型要同时盯住光伏余电和峰谷差
工作区配电网的典型困境是:中午光伏出力达到峰值时,常规负荷反而处于午休低谷,净负荷曲线出现明显的“鸭子嘴”。如果电动汽车只做无序充电,往往在上班到达后立即插枪,把充电需求集中在上午,既没有利用中午的光伏余电,还可能在晚高峰前再次充电,拉高峰谷差。这套MATLAB程序把工作区电动汽车当作“源-荷双重主体”,用粒子群算法求解全天每辆车的充放电功率序列,优化目标是配电网负荷方差最小,同时根据光伏出力与工作区负荷的偏差生成动态分时电价,让电动汽车用户在用能成本驱动下主动参与调节。对于研究V2G调度、微网需求响应和光伏消纳策略的人来说,这是一个可以直接改参数、跑数据的基准模型。
2. V2G模型的目标函数与约束条件:从源-荷二重性到配电网负荷方差
2.1 负荷方差目标函数的工程含义
配电网的稳定性不仅取决于最大峰谷差值,更取决于一天中每个时刻负荷相对平均水平的偏离程度。负荷方差越小,变压器和线路的利用率越平稳,电压波动也越容易控制。在这个模型里,工作区总的有功功率由三部分构成:常规负荷 P_load(t)、光伏出力 P_pv(t) 和电动汽车充放电功率 P_ev(t)。其中光伏出力在求和时取负值,因为它抵消了一部分负荷;电动汽车充电时 P_ev 为正,增加系统负荷,放电时 P_ev 为负,相当于给配电网注入功率。
目标函数可以写成:
% objective.m 核心片段 function f = objective(x, para) % x: 粒子位置,维度 = N_ev * T,表示每辆EV各时段充放电功率 % para: 结构体,包含常规负荷、光伏、电池参数等 T = para.T; P_load = para.P_load; % 1 x T 常规负荷 P_pv = para.P_pv; % 1 x T 光伏出力(正值) P_ev = reshape(x, para.N_ev, T); % 按车辆拆分 P_ev_total = sum(P_ev, 1); % 1 x T 所有EV聚合功率 % 净负荷:常规负荷 - 光伏 + EV充放电 P_net = P_load - P_pv + P_ev_total; P_avg = mean(P_net); f = sum((P_net - P_avg).^2) / T; % 负荷方差 end这里为什么用“常规负荷 - 光伏”而不是单独处理光伏?因为对配电网来说,真正需要平衡的是净负荷,光伏出力是负的确定性扰动。把P_pv放进目标函数里,等效于让EV去平滑“光伏-负荷”差值,而不只是平滑纯负荷。这个细节很多论文里会含糊过去,实际编程时如果忘记光伏取负,优化出来的EV策略会把午间的富余光伏当成需要“填平”的低谷,方向就完全反了。
需要特别留意的是,P_avg 是包含EV调节后的平均负荷,而不是调节前的平均负荷。这会让优化目标隐含地对EV总功率加上一个约束,如果所有EV同时大幅放电,P_avg 也会变化,目标函数并不是简单地让每个时刻离固定水平最近。实际运行中可以对比固定P_avg和动态P_avg两种处理方式,后者更接近配电网的能量平衡视角,也是这个模型默认采用的方式。
2.2 约束条件与soc_endzi.m的作用
没有约束的负荷方差最优解会让电动汽车一直放电到没电,或者一直充电到满,显然不可行。模型必须同时满足四个层面的约束:
| 约束类型 | 数学表达 | 实际意义 |
|---|---|---|
| 荷电状态范围 | SOC_min ≤ SOC(t) ≤ SOC_max | 防止过充过放,通常取0.1~0.9 |
| 出行需求 | SOC_end ≥ SOC_trip | 上班结束时要保留足够电量回家 |
| 充放电功率 | -P_dch ≤ P_ev(t) ≤ P_ch | 受车载充电机容量和电池倍率限制 |
| 时段可用性 | P_ev(t)=0 在非停车时段 | 只有车辆停在工作区才允许V2G |
表格里的 SOC_min 和 SOC_max 一般取 0.1 和 0.9 是为了保护锂电池寿命,但实际V2G项目里运营商往往会把可放电下限设在 0.2 以上,因为深度放电会显著加速电池容量衰减。这个参数在 parameter.m 里直接修改即可,改完后 SOC_end 的计算和罚函数都会同步变化。
其中“出行需求”最容易漏掉。原程序里 soc_endzi.m 专门负责这件事,它根据初始SOC和一天中逐时段的充放电功率,递推得到每个时刻的SOC,并检查结束时的SOC是否大于等于用户设定的出行阈值。递推式是:
% soc_endzi.m 递推计算SOC function [SOC, SOC_end] = soc_endzi(P_ev, SOC_init, para) % P_ev: 1 x T 单辆EV的功率序列, 充电为正, 放电为负 % para.Cap: 电池容量(kWh), para.dt: 调度步长(h) T = length(P_ev); SOC = zeros(1, T); SOC(1) = SOC_init; for t = 2:T SOC(t) = SOC(t-1) + P_ev(t) * para.dt / para.Cap; % 充电时P_ev>0,SOC上升;放电时P_ev<0,SOC下降 end SOC_end = SOC(T); end功率乘以时间得到电量,再除以电池容量得到SOC增量,这里需要与目标函数里的 P_ev_total 符号约定保持一致,否则充放电方向会颠倒。很多复现者在这里踩坑,把充电写成SOC(t) = SOC(t-1) - P_ev(t)*dt/Cap,结果优化出来的策略是中午电动汽车拼命放电,晚上再充电,恰好把光伏余电浪费掉。
2.3 约束嵌入方式:罚函数还是硬边界
在PSO中处理约束常见两种方式:硬边界和罚函数。对于SOC范围,可以在粒子位置更新后直接钳制,让粒子不产生越界的充放电功率;但出行需求是一个终端约束,钳制法无法直接处理,所以程序里通常采用罚函数法,在目标函数中加上对SOC_end不达标的惩罚项:
% objective.m 中增加出行约束惩罚 f = f + lambda * max(0, para.SOC_trip - SOC_end)^2;lambda 取较大值,比如 1e5。注意 lambda 太小会导致优化结果“宁可违反出行约束也要平滑负荷”,那样生成的策略看起来很美,实际上车主根本不会接受。我在这个模型里把 SOC_trip 设为 0.4,意思是上下班往返消耗约30%电量,保留40%的底线。如果用户实际通勤距离更长,需要修改 parameter.m 中的 SOC_trip。罚函数系数也不是越大越好,过大会让目标函数数值范围接近 lambda 的量级,粒子群在迭代初期很难分辨不同粒子的好坏,建议先设 1e3 看一次结果,如果 SOC_end 仍然低于阈值,再逐步增大。
3. 粒子群算法求解V2G充放电策略:程序结构与参数配置
3.1 文件结构与调用链
拿到 v2g程序.rar 后,解开压缩包可以看到一组 .m 文件和两个文档。各文件职责如下:
| 文件 | 作用 |
|---|---|
| v2g_main.m | 主入口,设置算例并启动优化 |
| parameter.m | 定义电池、电网、EV数量等全局参数 |
| data.xlsx | 存放光伏出力、工作区常规负荷、EV初始SOC等原始数据 |
| objective.m | 计算负荷方差目标函数和罚函数 |
| PSOFUN.m | 粒子群优化主循环,迭代搜索最优功率序列 |
| soc_endzi.m | 递推SOC并返回终端SOC值 |
| car_load_main.m | 计算EV数量、停车时段、充电功率边界等 |
调用关系是:v2g_main 首先调用 parameter.m 将参数载入结构体,然后读取 data.xlsx,生成每一辆EV的初始SOC和接入时段矩阵,再调用 PSOFUN.m 启动粒子群。PSOFUN 每评估一个粒子就调用一次 objective.m,objective 内部通过 soc_endzi.m 得到最终SOC用于罚函数计算。整个链条中 data.xlsx 是最容易出问题的环节,后面第5章会专门说。
3.2 粒子编码与搜索空间
粒子位置编码直接决定问题规模。这里的决策变量是每辆EV在优化时段内的充放电功率,所以粒子是一个 N_ev × T 的矩阵。比如 10 辆车、24 个时段,那么单个粒子维度就是 240。PSO 在这种高维连续空间上运行,速度更新公式是标准形式:
% PSOFUN.m 粒子群核心更新 for iter = 1:maxIter w = w_max - (w_max - w_min) * iter / maxIter; % 惯性权重线性递减 for k = 1:N_pop vel(k,:) = w * vel(k,:) + c1 * rand * (pbest(k,:) - x(k,:)) + c2 * rand * (gbest - x(k,:)); x(k,:) = x(k,:) + vel(k,:); % 边界钳制:功率不能超过充放电上限 x(k,:) = min(max(x(k,:), -P_dch_max), P_ch_max); end % 评估每个粒子 for k = 1:N_pop fit(k) = objective(x(k,:), para); if fit(k) < fit_pbest(k) fit_pbest(k) = fit(k); pbest(k,:) = x(k,:); end end [min_fit, idx] = min(fit_pbest); if min_fit < fit_gbest fit_gbest = min_fit; gbest = pbest(idx,:); end end这段代码里需要说明几个参数:w_max 取 0.9,w_min 取 0.4,c1 和 c2 都取 1.49。w 递减是经典做法,前期全局搜索,后期局部收敛,避免粒子在最优解附近震荡。速度 vel 的初值设为 0 或很小的随机数,如果初值太大,粒子容易直接飞出边界并大量堆积在边界上,导致后续迭代失效。
速度钳制往往会导致粒子长期在边界处堆积,因为越界粒子的速度被清零后,下一次迭代只能靠 pbest 和 gbest 拖回可行域,搜索效率很低。位置钳制则简单粗暴,直接把越界分量拉回边界,代价是损失粒子多样性。我的经验是先用位置钳制,如果多次运行结果差异大,再给速度限制 vmax,取每维搜索空间的20%即可。
3.3 如何设置种群数和迭代次数
V2G模型的决策变量维度不低,但并不是越多越好。我测试这个程序时,把种群数从 30 逐渐加到 80,负荷方差指标只下降了约 2%,但计算时间从 40 秒增加到近 4 分钟。对于 24 时段、10 辆车以下的问题,建议种群数 40、迭代 100 次即可。如果是 100 辆EV的大规模算例,粒子维度达到 2400,普通PSO收敛非常慢,需要改成并行评估或用粒子群变体,比如量子粒子群、混沌惯性权重。parameter.m 里对应的字段一般为 N_pop 和 maxIter,改这两个值就能控制精度和耗时。
3.4 运行主程序与数据载入
打开 MATLAB,将解压后的文件夹设为当前工作目录,直接运行:
v2g_main或者按 F5。运行结束后,工作区会出现 gbest、fit_gbest、SOC_all 等变量。gbest 就是优化后的最优充放电功率矩阵,行是每辆车,列是每个时段。fit_gbest 是最终负荷方差。注意不要用编辑器里的“运行”按钮直接跑带参数函数,v2g_main 是一个独立脚本,不依赖外部输入。
data.xlsx 的读取建议用 readmatrix 而不是老旧的 xlsread,R2019a 之后的版本都支持:
% v2g_main.m 中读取数据片段 data = readmatrix('data.xlsx'); % 若首行为表头,可加 'Range', 'A2' t = data(:,1); % 时段编号 P_pv = data(:,2); % 光伏出力 P_load = data(:,3); % 工作区常规负荷readmatrix 会自动处理数值和文本混合列,但如果表格里有日期时间列,MATLAB 可能把它读成 datetime 类型,后续参与运算时记得用 double(t) 显式转换。如果 MATLAB 版本比较老,需要改用 xlsread,并将输出用 cell2mat 处理。
4. 动态分时电价机制与出行需求约束的联合实现
4.1 动态分时电价的计算逻辑
动态分时电价不是固定峰平谷三段,而是根据“光伏出力与工作区负荷的偏差”逐时段计算。偏差越大,说明光伏富余越多,此时应该降低电价,鼓励电动汽车多充电;反之,当净负荷接近峰值时,提高电价,引导电动汽车放电。电价基准值和调整系数的设定会影响电动汽车用户的响应程度,也会影响优化后的负荷方差结果。
假设基础电价为 0.8 元/kWh,电价计算如下:
% 动态分时电价生成函数 function price = dynamic_price(P_load, P_pv, base_price, k) P_net = P_load - P_pv; % 未计入EV的净负荷 P_avg = mean(P_net); % 偏差归一化到[-1,1] dev = (P_net - P_avg) / max(abs(P_net - P_avg)); % 电价在基础电价上下浮动,偏差越大价格越高 price = base_price .* (1 + k * dev); price = max(price, 0.3); % 设置下限,防止负电价 end这里 k 是价格弹性系数,设为 0.4。画出来的动态电价曲线通常呈现“中午低、早晚高”的形态,这与光伏出力的倒钟形曲线正好错位。价格下限设置在 0.3 元/kWh,是为了避免用户在光伏最富余时出现反向激励,如果价格降到负值,电动汽车反而会为了赚取负电价而故意用电,这不符合本模型的设计假设。
需要强调的是,这个 price 序列在PSO优化之前就已经算好了,它只由原始净负荷决定,不随粒子位置的更新而改变。这与“动态反推电价-策略循环收敛”的完全动态博弈模型不同,本模型属于“半动态”:电价对负荷偏差做出反应,但EV功率变化不会反过来重新生成电价。这样做简化了问题,工程上更容易落地,因为调度中心可以前一天晚上公布次日分时电价,电动汽车用户根据电价自行安排充放电。
4.2 用户充放电满意度量化
用户不是无条件参与V2G的,满意度主要由两个因素决定:一是参与后的费用相比不参与时的变化,二是出行需求是否被满足。费用包括充电电费和放电收益:
% 用户费用计算 cost_user = sum(price .* max(P_ev_total, 0)) ... % 充电费用 - sum(price .* max(-P_ev_total, 0)); % 放电收益充电时段付出电费,放电时段获得收益。如果优化后费用比无序充电还要高,用户自然不愿意。因此,调参时除了看负荷方差,还要检查 cost_user 是否在可接受范围内。如果想体现满意度,可以在目标函数中增加费用项,让优化同时兼顾电网侧和用户侧。但原程序把费用拆出来独立计算,方便对比不同电价策略下的用户收益,这种做法在敏感性分析里更灵活。
4.3 出行需求约束如何与电价联动
出行需求约束不仅指终端SOC,还包括停车时段限制。工作区电动汽车通常早上8点到达,下午17点离开,只有 9:00-17:00 这个窗口允许V2G。在电价低谷(光伏富余的11:00-14:00)充电,在电价高峰(9:00-10:00)放电,最终SOC又必须满足回家需求,这实际上是在“低买高卖”和“保住底电”之间博弈。
car_load_main.m 的作用就是根据停车时段和每辆车的初始SOC,生成每个时刻的可用性矩阵 available,在约束中把非停车时段的功率强制置零:
% 非停车时段功率清零(在目标函数内部执行) P_ev = P_ev .* available; % available = 1 表示停车且允许V2G如果算例中有商用车或者不同到达时间的EV,car_load_main.m 里还会根据上班时间矩阵 ETA 和下班时间矩阵 ETD 生成每个时段的 available。代码里要注意 MATLAB 矩阵索引从 1 开始,所以 8 点到达对应第 8 个时段,如果时段间隔不是 1 小时,还要转换成对应的离散索引。如果忽略这个矩阵,粒子群会把功率分配到夜间,而夜间车辆根本不在工作区,结果完全是虚构的。这是很多复现者栽跟头的地方。
5. 从MATLAB代码到有效结果:验证方法与常见坑
5.1 做三组对比实验验证模型效果
只看优化后的负荷方差数字没有说服力。建议在 v2g_main.m 中增加一个 mode 变量,分别跑三种模式:无序充电(车辆到达后立即以固定功率充满)、单向有序充电(只充不放)、V2G双向充放电。记录每组结果的负荷方差、光伏消纳率、用户平均费用。光伏消纳率定义为实际消纳光伏电量占光伏总出力的比例,这个指标直接反映V2G对“鸭子曲线”的削峰效果。通常V2G模式相比无序充电能减少负荷方差 30% 以上,光伏消纳率提高 15 个百分点,这就是模型有效的证据。
5.2 画收敛曲线和负荷对比曲线
从 PSOFUN.m 返回的 fit_gbest_history 可以画出迭代收敛过程:
figure; plot(fit_gbest_history, 'LineWidth', 1.5); xlabel('迭代次数'); ylabel('最优负荷方差'); title('粒子群收敛曲线'); grid on; figure; t = 1:24; plot(t, P_load, 'k--', t, P_load - P_pv + gbest_total, 'r-o'); legend('常规负荷', '净负荷+EV调节'); xlabel('时段'); ylabel('功率/kW');如果收敛曲线在 20 代以内就平了,说明问题偏简单,可以减小种群数;如果 100 代还在抖动,说明惯性权重下界太大或罚函数权重太小。第二张图能直观看到EV调节后的净负荷曲线是否比原始曲线更平坦。
5.3 常见报错与调参建议
我排查这套程序时遇到最多的三类问题:一是 data.xlsx 的日期列被 MATLAB 读成 datetime 类型,而目标函数里当数值用导致类型错误,处理办法是读取后加 double() 转换;二是停车时段与时段编号错位,比如 9 点到 10 点被写成第 9 和第 10 个时段,实际上应该映射到时段索引 9 到 10,注意边界是否包含;三是 SOC 递推时电量单位不一致,data.xlsx 里功率是 kW,步长是小时,直接相乘得到 kWh,再除以电池容量 kWh 即可,不要额外乘 1000。
参数调整方面,如果早熟明显,把 w_max 提到 0.95 并让 w_min 降到 0.3;如果结果总在约束边界上,优先增大罚函数系数而不是减小搜索边界。如果罚函数系数超过 1e6,目标函数梯度会被罚项主导,粒子群可能从一个不可行解跳到另一个不可行解,此时需要回头检查 SOC_trip 是否设置得过高。最后建议把种群数设为 40,迭代 100 次,作为速度和精度的折中。
本文还有配套的精品资源,点击获取