1. V2G实时调度解决什么问题:从无序充电的网损压力说起
做配电网仿真这几年,我越来越确定一件事:电动汽车渗透率上来之后,最先暴露问题的不是上级变压器,而是网损。之前在一个IEEE 33节点的测试系统上跑无序充电场景,系统基础网损大约202 kW,接入一批下班集中充电的电动汽车后,晚高峰网损直接涨到270 kW上下,末端节点电压跌破0.93 p.u.。后来把同一批车改成基于V2G的实时调度策略,网损又被压回210 kW左右,配电网不仅没被电动车拖垮,反而因为削峰填谷变得更健康。这篇内容就把我做这套MATLAB代码的完整思路、模型设计和实际调试过程写透,主要面向正在做电动汽车调度、配电网优化运行的研究生和工程师。
1.1 无序充电为什么可怕:网损与负荷堆叠的物理逻辑
很多人一开始不太理解网损为什么会恶化得那么快。这里面的物理原理其实很直接:线路上的有功损耗等于电流平方乘以线路电阻,也就是经典的P_loss = I²R。电流不是线性增加,损耗却是按平方关系往上走的。假设一条支路原本流过300 A电流,电阻0.5 Ω,损耗是45 kW;如果因为电动汽车集中充电,电流变成500 A,损耗就直接跳到125 kW,接近原来的三倍。这个平方关系决定了电网在负荷高峰期对新增用电极其敏感。
更麻烦的是时间重叠。电动汽车用户的下班充电行为高度集中在18:00到22:00,这个时段恰好是居民用电晚高峰。空调、电热水器、照明这些基础负荷还没降下去,充电负荷又叠上来,结果就是配电网"雪上加霜"。我在仿真中观察到,峰值时段部分重载支路的电流已经逼近线路载流量上限,末端节点电压也明显偏低。电压一旦跌落,恒功率充电桩为了维持同样的输出功率会进一步拉大电流,形成一种恶性循环:电压越低、电流越大、网损越高。这也是我在代码里坚持把节点电压约束放开、专门做潮流校验的原因。
1.2 V2G的本质:把电动汽车从负荷变成双向可调度资源
V2G全称Vehicle-to-Grid,核心就是让电动汽车具备双向充放电能力:既可以充电从电网取电,也可以在需要时把电池里的电反送给电网。这样一来,车就不再是纯粹的"负荷",更像是一个分布在用户侧的可移动储能单元。
这跟普通的有序充电有本质区别。有序充电只是延迟充电开始时间,比如把充电时段挪到23:00之后,它仍然只能"单向取电"。V2G则能在晚高峰让一部分车把电放出来,直接削减净负荷峰值,然后在深夜低谷电价时段再把这些电补回去。放电在数学上就等价于在节点上接入了一个负的负荷,这给调度优化带来了真正的自由度。
当然,V2G的实际落地有很多工程约束:电池寿命、用户出行需求、充电桩是否支持双向充放电、放电电价机制等等。但从调度策略研究的角度来说,V2G把"电动汽车参与电网调节"从理论变成了一个可建模、可优化、可仿真验证的问题。我这套MATLAB代码,就是抓住V2G最核心的双向功率调节能力,把它嵌入到配电网实时调度闭环中。
1.3 实时调度与日前/静态调度的关键区别
很多人做电动汽车调度用的还是日前调度:基于对第二天负荷和电动汽车接入情况的预测,一次性生成24小时充放电计划。这种方案在数据准确的时候效果不错,但问题在于实际运行中不确定性太大。用户可能晚到一个小时,可能提前离开,可能SOC初值和预测完全不一样,这些偏差累积起来,会让日前计划的执行效果大打折扣。
实时调度的思路是滚动优化,不是一次定终身。我的代码把一天分成96个时段,每个时段15分钟。每到一个新的调度时刻,程序会重新读取当前各节点的实测负荷、每台电动汽车的实际SOC、接入状态,然后基于未来几个小时的预测信息重新求解一次优化模型,只执行当前时段的充放电指令,等下一个时刻再重复这个过程。这样等于每15分钟就把计划"校准"一次,电动汽车的随机行为只要不是极端情况,都能被及时修正。
这种滚动闭环结构是"实时调度策略"区别于"日前调度"的核心,也是代码实现上的主要复杂度来源。我后续会详细拆解滚动优化的具体实现,这里先建立一个概念:实时调度方案的骨架是"预测—优化—执行—反馈—再优化"的循环,而不是"一次性算完、按计划执行"。
2. 调度策略建模:目标函数、决策变量与约束边界设计
2.1 调度周期与决策变量拆解
建模第一步是把"调度"翻译成数学语言。时间轴上,我按15分钟一个时段来离散化,一天就是96个时段,这也是目前配电网自动化和电力市场中最常见的调度粒度。再短到5分钟会让求解规模暴涨,再长到1小时又不够灵敏,15分钟算是一个在实际工程和学术研究中都普遍接受的折中。
决策变量包括三类。第一类是每台电动汽车每个时段的充电功率,记为P_ch(i,t);第二类是放电功率P_dis(i,t),单位都是kW;第三类是一个0-1整数变量,表示每台车在某个时段是处于充电状态还是放电状态。为什么需要整数变量?因为同一时刻一台车不可能既充电又放电,这不仅是物理上不允许,模型上也会导致目标函数出现虚假的"充电又放电"最优解。通过整数变量u(i,t),我让充电功率和放电功率互相排斥:u=1时允许充电、禁止放电,u=0时允许放电、禁止充电。
功率上下界由充电设备和车载充电机共同决定。我的基础场景用的是7 kW交流慢充桩,这也是小区和停车场最常见的配置;如果你想改60 kW直流快充场景,只需改Pmax这一个参数,模型框架完全不用动。每台车有电池容量E_cap,我这里设为40 kWh,和主流纯电车型的电池容量比较接近。
2.2 目标函数:网损最小化如何在模型中落地
目标函数的选择直接决定了调度策略的行为倾向。这套代码的核心目标是最小化全网有功网损,数学上写成:
min Σ_t Σ_ij r_ij·(P_ij,t² + Q_ij,t²) / V_i,t²
其中r_ij是支路ij的电阻,P_ij和Q_ij是支路流过的有功和无功功率,V_i是节点电压幅值。这个表达式来自交流潮流的支路损耗公式,物理含义非常清楚:支路电流越大、电阻越大,网损越大。
关键的问题是:这个目标函数带有非线性分式项,直接扔给求解器会非常难解。我在实际代码里采用了配电网优化中最常用的DistFlow二阶锥松弛形式。通过引入辅助变量,把非线性的潮流方程松弛成二阶锥约束,既保证了求解效率,又能获得不错的精度。如果你不想引入二阶锥,还有一个更工程化的近似做法:用前一迭代的电压幅值把网损表达式线性化,先求解,再用潮流计算结果更新电压,反复迭代几次。
在目标函数里我还留了一个可选的充电成本项,加权系数设为α。当α=0时就是纯网损最小化;当α取某个正值时,模型会同时兼顾车主充电费用,这时调度策略会在降低网损和降低用户成本之间寻找平衡。实际场景中这两个目标往往不完全冲突,因为低谷电价时段充电既省钱又能填谷降损,这是V2G调度最理想的运行区间。
2.3 约束条件的完整清单
模型里最花功夫的不是目标函数,而是约束条件。我在代码中把约束分成五大类,缺一不可。
第一是配电网安全约束,包括节点功率平衡、线路容量上限和节点电压上下限。功率平衡方程描述的是每个节点的注入功率与流出功率相等;线路容量约束保证任何支路不过载;节点电压约束我初始设置为0.95到1.05 p.u.,这是配电网运行规程里的常规要求。
第二是电动汽车自身约束。每台车的SOC需要按充放电功率和时间步长滚动更新,考虑充电效率和放电效率。SOC不能超过电池上下限,我设的是0.2到0.95,留一点余量给电池保护。
第三是用户出行需求约束,这是整个模型里最硬的约束。每台车都有接入电网的时间段、离网时间和期望的离网SOC。比如用户18:00到家插上充电枪,第二天早上8:00要出发,期望出发时SOC不低于90%。调度模型无论如何优化,都必须保证这个目标可达。我的代码里对每台EV都维护一个"最小充电需求曲线",如果模型发现某台车在接入时间内根本充不到目标SOC,就意味着约束不可行,需要调整参数而不是暴力求解。
第四是充放电互斥约束。这个我在2.1已经提到,通过整数变量实现。
第五是变压器/变电站容量约束。整个配电网从上级变电站取电的功率不能无限大,这个约束在电动汽车渗透率高的场景下特别重要,它直接限制了从电网侧"借电"的上限。
具体约束表达式整理如表所示:
| 约束类型 | 数学形式 | 说明 |
|---|---|---|
| SOC动态约束 | SOC(i,t+1) = SOC(i,t) + (η_ch·P_ch,i,t − P_dis,i,t/η_dis)·Δt/E_cap | η_ch取0.95,η_dis取0.92 |
| 充放互斥 | P_ch,i,t ≤ u(i,t)·P_max;P_dis,i,t ≤ (1−u(i,t))·P_max | 同一时刻只能充或放 |
| SOC上下限 | 0.2 ≤ SOC(i,t) ≤ 0.95 | 电池保护与安全裕度 |
| 离网SOC要求 | SOC(i,T_dep) ≥ SOC_target | 车主出行硬约束 |
| 节点电压 | 0.95 ≤ V_j ≤ 1.05 p.u. | 配电网安全运行范围 |
| 线路容量 | S_ij ≤ S_ij,max | 避免支路过载 |
2.4 为什么用YALMIP+求解器而不是智能算法
这个问题几乎每次聊这个课题都会被问到。很多初学者习惯用粒子群PSO或者遗传算法GA去优化,因为思路直观、不需要推导梯度,而且MATLAB里写起来也不难。但放到实时调度这个场景下,智能算法有几个硬伤:一是每次求解结果都不一样,同样的输入跑两次可能给出不同的充放电指令,这对工程落地是致命的;二是不容易严格保证约束满足,特别是节点电压和线路容量这类硬约束,智能算法的罚函数处理方式很难让人放心;三是求解速度不稳定,种群一大、迭代一多,一个时段可能要好几分钟,滚动优化根本跑不起来。
所以我选择用YALMIP作为建模语言,底层调用商业求解器。YALMIP的好处是不需要手工把问题转化为特定的求解器格式,你只需要用数学表达式描述目标函数和约束,它会自动完成模型转换。底层求解器我用的是MATLAB自带的intlinprog,它在中等规模混合整数线性规划上表现够用;如果你的场景特别大,直接替换成Gurobi或CPLEX,代码逻辑一行都不用改。
顺便说一句,最近深度学习的东西很火,也有人问能不能用深度强化学习DRL来做实时调度。DRL的优势在于它可以离线学习策略、在线快速决策,非常适合超大规模场景。但代价是你很难保证每个时段的解都严格满足所有约束,而且训练过程对数据质量和环境仿真要求很高。我的观点是:先把这个基于数学规划的最优调度模型跑通、跑稳,再考虑要不要用DRL去逼近它,才是更稳妥的技术路线。这就是代码选型背后的实际考量。
3. MATLAB代码架构:从IEEE 33节点到滚动优化闭环
3.1 测试平台选择:IEEE 33节点配电系统
代码的配电网模型我选的是IEEE 33节点标准测试系统。这是配电网优化研究里最常用的算例:系统额定电压12.66 kV,包含33个节点、32条支路,有5个联络开关但常态下处于打开状态,整体呈辐射状结构。总有功负荷约3.7 MW,无功负荷约2.3 Mvar,规模不大不小,既能体现配电网的辐射状潮流特性,又不会因为节点太多导致仿真速度失控。
选择IEEE 33节点的另一个原因是结果可校验性强。这个系统的基准潮流数据、网损数据在公开文献里都能查到,跑出来的结果对不对很容易对照判断。如果你的课题需要更接近真实场景,可以把load_case33.m这个函数替换成自己馈线系统的节点导纳矩阵或支路参数表,后面的调度代码完全不需要改。这种"网络数据与优化逻辑解耦"的设计,是这套代码可扩展性的基础。
3.2 代码模块划分
整理代码架构,我习惯把一个完整的实时调度程序拆成几个职责单一的函数。这套代码的核心模块我设计为:
| 文件/函数 | 职责 | 关键输出 |
|---|---|---|
| main_rolling_ev_dispatch.m | 滚动优化主循环 | 各时段调度结果、网损曲线 |
| load_case33.m | 加载IEEE 33节点网络数据 | 节点、支路、基荷数据 |
| ev_profile_generator.m | 生成电动汽车场景 | 接入时段、SOC初值、离网SOC目标 |
| build_optimization_model.m | 构建YALMIP优化模型 | 目标函数句柄、约束集合 |
| solve_dispatch_one_step.m | 求解单个时段优化问题 | 当前时段充放电功率 |
| compute_pf_forward_backward.m | 前推回代潮流计算 | 节点电压、支路潮流、网损 |
这种划分方式最大的好处是排错容易。仿真结果不对时,你可以先单独跑潮流计算函数,确认网络模型没错;再单独跑场景生成器,确认EV参数合理;最后才去查优化模型,问题范围一下子缩小很多。很多第一次写这类程序的读者把全部代码堆在一个脚本里,一旦报错就得从头排查,效率非常低。
3.3 核心优化模型代码片段
build_optimization_model.m是整个程序的灵魂。下面这段代码是YALMIP建模的核心片段,展示决策变量、SOC动态和互斥约束的组织方式:
function [Objective, Constraints] = build_optimization_model(Net, EV, Window, dt) N_ev = EV.N_ev; T = Window; % 滚动窗口长度 Pmax = EV.Pmax; % 最大充放电功率 Ecap = EV.E_cap; % 电池容量 eta_ch = EV.eta_ch; eta_dis = EV.eta_dis; Pch = sdpvar(N_ev, T, 'full'); % 充电功率决策变量 Pdis = sdpvar(N_ev, T, 'full'); % 放电功率决策变量 u = binvar(N_ev, T, 'full'); % 0-1互斥变量 SOC = sdpvar(N_ev, T+1, 'full'); % SOC轨迹 Constraints = []; % 初始SOC由当前实测状态给定 Constraints = [Constraints, SOC(:,1) == EV.SOC_initial]; for t = 1:T % SOC动态更新 Constraints = [Constraints, ... SOC(:,t+1) == SOC(:,t) + ... (eta_ch*Pch(:,t) - Pdis(:,t)/eta_dis)*dt ./ Ecap]; % 充放电互斥 Constraints = [Constraints, ... Pch(:,t) <= u(:,t)*Pmax, ... Pdis(:,t) <= (1-u(:,t))*Pmax, ... Pch(:,t) >= 0, Pdis(:,t) >= 0]; % 节点功率平衡、电压约束由潮流接口补充 % Constraints = [Constraints, ...]; end % SOC上下限与离网需求 Constraints = [Constraints, 0.2 <= SOC <= 0.95]; % 离网时段的SOC目标在滚动主循环中按接入状态动态加入 % 目标函数:全网统计网损最小(网损由潮流模型计算得到) % 这里先定义网损表达式,再通过外部参数传入 Objective = sum(sum(Net.R_br .* (Net.I_br_square))) * dt; end注意代码里的网损项我用了Net.I_br_square这个变量来表示支路电流平方,在实际工程代码中它并不是一个独立的决策变量,而是通过DistFlow潮流方程与节点注入功率耦合的。为了让读者能上手,代码工程里通常会把潮流方程单独封装成函数,在每次优化求解后调用compute_pf_forward_backward去验证和修正。这里展示的只是建模框架,完整可运行的工程实现还需要把潮流方程展开写入约束集。
3.4 前推回代潮流计算与网损统计
网损到底怎么算?我在代码里提供了一套前推回代法的潮流计算实现。前推回代适用于辐射状配电网,原理很直观:从末端节点往回推,根据负荷需求和支路参数逐段累加支路功率;再从首端节点往前推,根据支路功率和电压降逐段更新节点电压。两步交替迭代,直到前后两次电压偏差小于收敛阈值。
代码的核心结构大致是:
function [V, P_loss] = compute_pf_forward_backward(Branch, Node, S_load, V_root) V = ones(Node.N, 1) * V_root / Node.V_base; % 电压初值 tol = 1e-6; for iter = 1:100 V_old = V; % 回代:由末端向首端计算支路功率 S_branch = backward_calculate(Branch, S_load, V); % 前推:由首端向末端更新节点电压 V = forward_calculate(Branch, S_branch, V_root); if max(abs(V - V_old)) < tol break; end end % 统计全网有功网损 P_loss = sum(real(S_load) .* (1 - abs(V).^(-2))); % 简化示意 end这里我做了简化示意,实际代码中支路功率和电压的计算需要按配电网的辐射状拓扑展开。网损统计要区分两个口径:一个是瞬时网损功率,单位kW,用来观察调度策略在峰值时段的压制效果;另一个是日累计网损电量,单位kWh,用来衡量一天下来总共损耗了多少电能。这两个指标在结果分析里我都会用到。
3.5 滚动优化主循环实现
滚动优化主循环的逻辑决定了"实时调度"如何被真正执行。在main_rolling_ev_dispatch.m中,我维护了一个时间指针,每15分钟推进一次。每次循环做四件事:读取当前各节点基荷、读取每台EV当前SOC和接入状态、构建未来一段时间的优化模型、求解并只执行当前时段的功率指令。
这里有一个关键参数:滚动窗口长度。我把优化窗口设为24个时段,也就是未来6小时。窗口太短会忽略稍远一点的充电需求,导致调度策略"只顾眼前";窗口太长又会因为预测信息不可靠和求解规模增大而变慢。6小时是我经过多组对比测试后选下来的经验值。
主循环的核心思路用伪代码表示为:
for t_start = 1:96 % 更新EV接入状态与当前SOC EV = update_ev_status(EV, t_start); % 构建未来24时段的优化模型 [Obj, Cons] = build_optimization_model(Net, EV, 24, dt); % 求解 results = solve_dispatch_one_step(Obj, Cons); % 只执行当前时段的充放电指令 execute_power_command(results, t_start); % 用实际量测数据刷新EV状态,进入下一轮 end之所以只执行当前时段,是因为下一轮循环会带着新的实测数据重新优化,之前求解出来未来时段的结果只是"参考计划",并不真正执行。这就是滚动优化和开环优化的本质区别,也是"实时"两个字在代码层面最直接的体现。
4. 仿真结果对比:无序充电与V2G实时调度到底差多少
4.1 场景参数设置
仿真测试我搭了两组典型场景。基础场景接入50台电动汽车,高渗透场景接入100台。每台车电池容量40 kWh,最大充放电功率7 kW,充电效率0.95,放电效率0.92。车辆接入电网的时间集中在18:00到22:00之间,次日早上6:00到8:00陆续离开,初始SOC在0.3到0.8之间随机分布,离网期望SOC统一设置为0.9。基荷采用IEEE 33节点标准负荷曲线并按日峰谷系数展开。
对比基准设了三种策略:一是无序充电,车接入后立即以额定功率充电直到充满;二是有序慢充,只优化充电开始时间,不允许放电;三是本文的V2G实时调度,允许车辆在晚高峰放电、低谷时段充电,以网损最小为目标滚动优化。
4.2 三种策略的网损与电压对比
跑完96个时段后,我把一天的仿真结果做了汇总,得到下面这组典型数据:
| 指标 | 无序充电 | 有序慢充 | V2G实时调度 |
|---|---|---|---|
| 峰值网损(kW) | 271.4 | 236.8 | 213.2 |
| 日网损电量(kWh) | 2687 | 2321 | 2105 |
| 最低节点电压(p.u.) | 0.926 | 0.942 | 0.951 |
| 晚高峰净负荷峰值(kW) | 4382 | 4105 | 3866 |
需要说明的是,这是IEEE 33节点配电网在特定负荷曲线和EV参数下的仿真结果,不是放之四海而皆准的绝对数值。但趋势是清晰且可复现的:无序充电场景下网损最严重,日网损电量比V2G方案高出约27%;有序慢充只靠延迟充电时间就能把网损降低一部分,但因为没有放电能力,它无法在晚高峰真正削减净负荷;V2G实时调度则实现了最低的网损、最高的最低电压和最平缓的净负荷曲线。
4.3 为什么V2G表现更好:从负荷曲线的角度解释
网损下降的原因并不神秘,归根结底还是回到了I²R的平方关系。V2G调度让一部分电动汽车在19:00到21:00这个负荷尖峰时段反向放电,相当于把这个时间段的净负荷整体往下压。负荷下降了,线路电流随之减少,网损下降的幅度比负荷下降的幅度更明显——因为损耗是电流的平方项。
更值得关注的是最低节点电压的改善。在无序充电场景下,末端节点电压最低到了0.926 p.u.,已经超出规程允许的运行范围。V2G场景中,部分末端接入的电动车在高峰时段放电,相当于就地提供了无功和有功支撑,末端电压被抬升到0.951 p.u.以上,满足安全运行要求。这个指标很多时候比网损本身更关键,因为它直接关系到供电质量和设备安全。
此外,V2G调度还缩小了配电网的峰谷差。晚高峰削峰、深夜填谷,变电站的负荷曲线变得平坦,这意味着变压器的容量利用率更高、整体运行更经济。虽然网损最小化目标本身并没有直接奖励"平抑峰谷差",但峰谷差的改善是削峰填谷行为的自然结果。
4.4 参数敏感性:EV渗透率与SOC离网要求
仿真做完,我又做了两组敏感性分析,目的是搞清楚调度策略的收益在什么条件下会放大、什么条件下会缩小。
第一组是电动汽车渗透率从10%逐步增加到60%。结果符合预期:渗透率越低,V2G可调资源越有限,网损优化空间越小;渗透率越高,V2G相较于无序充电的优势越明显,日网损电量的降幅从10%左右扩大到30%以上。但渗透率超过50%之后,改善幅度开始放缓,因为这时变电站变压器容量约束开始成为瓶颈,即使车辆还有放电能力,电网侧能接受的馈入功率也有限了。
第二组是离网期望SOC的要求。把离网SOC目标从0.9放宽到0.7,调度自由度明显增加,网损进一步下降约8%。这说明用户侧的需求约束对调度效果影响很大。在实际V2G运营中,运营商可以通过电价激励引导用户设置更低的离网SOC需求,把电池里更多的电量释放给电网使用。但这里有个度的问题:放得太低会影响用户体验,而且电池深度放电会加速循环老化,这在工程方案设计时需要综合权衡。
5. 调试与落地经验:实时调度代码最常见的几个坑
5.1 潮流不收敛的排查顺序
前推回代法本身实现简单,但实际运行中确实会遇到不收敛的情况。我在调试时遇到过三次,每次原因都不一样。
第一次是配电网数据本身有误。某个支路的电阻填错了一个数量级,末端负荷根本推不上去,电压一路跌到0.4 p.u.以下,潮流当然无法收敛。排查方法是先用公开的IEEE 33节点基准数据跑一遍潮流,验证程序本身的正确性,再去改你自己的网络参数。
第二次是重负荷场景下迭代震荡。电动汽车渗透率拉到60%以后,某些时段节点功率过大,前推回代法在迭代过程中电压反复跳动,不收敛。解决办法是给电压更新加一个阻尼系数,比如V_new = V_old + λ(V_calc − V_old),λ取0.5到0.8,震荡马上就能压下来。
第三次是末端负荷过重导致电压跌破收敛域。这种场景严格来说不是潮流算法的问题,而是电网本身已经无法安全承载这个负荷水平。我会优先检查是不是EV充电功率设得太高,或者节点基荷数据有误;如果确实需要维持这么高的负荷,就得考虑切负荷或者增加无功补偿,这已经不是靠调整潮流算法能解决的了。
5.2 求解器报Infeasible的第一反应:约束冲突
用YALMIP加求解器最让人头疼的就是报Infeasible,模型不可行。刚开始我盯着报错信息看半天,毫无头绪,后来总结出几个固定的排查顺序,效率高了很多。
第一个要查的是离网SOC约束是否可达。一台车18:00接入,SOC只有0.3,要求次日6:00离网时达到0.9,中间12小时按7 kW充电能充入84 kWh,电池容量40 kWh,理论上肯定够。但如果接入时间是20:00,离网时间是次日5:00,只有9小时,中间还要预留放电时段,那这个目标就可能达不到。我在代码里加了预检验:先算一遍"如果全程按最大功率充电,离网时能达到的最高SOC",如果这个值低于目标SOC,直接向用户报告参数冲突,而不是让求解器去撞墙。
第二个要查的是充放电互斥约束。这里有一个特别常见的错误写法:把充电功率约束写成Pch <= uPmax,把放电功率约束写成Pdis <= uPmax,结果u=0时放电也被禁掉了,模型当然无解。正确写法我在3.3代码里已经给出,放电约束必须用(1-u)来乘。
第三个要查的是电压约束过紧。0.95 p.u.的电压下限在重载场景下很可能无法满足。排查时可以先把电压下限暂时放宽到0.93,如果模型马上变得可行,说明问题出在安全约束和调度目标之间有冲突,要么调整EV接入策略,要么增加无功补偿,要么接受略低的电压水平。
5.3 实时性优化:如何在10秒内完成单步求解
滚动优化的每个时段都要调用一次求解器,如果单步求解耗时太长,"实时"就名存实亡了。我在实测中发现,求解时间主要被三个因素拖累。
第一个是目标函数中的非线性项。网损表达式如果写得太复杂,二阶锥约束规模会膨胀,求解时间成倍增加。解决思路是能线性化的尽量线性化,网损可以先由上一轮的潮流结果线性化后再作为目标函数,迭代几次就能逼近精确结果。
第二个是常数矩阵在循环里重复计算。有些读者会把支路参数、基荷数据在每轮优化中重新读取和处理,这非常浪费时间。正确做法是在主循环之前把所有静态数据一次性计算好并缓存,每轮只更新变化的EV状态和时间序列。
第三个是默认求解器参数和初始值。YALMIP允许给决策变量设置初始猜测值,我把上一时段的最优解作为当前时段的初值,求解时间平均能缩短30%到40%。另外,求解器的终止容差也不用设得太苛刻,网损优化本身允许一定的近似,把容差从1e-6放宽到1e-4,对结果精度影响很小,但速度提升明显。
做完这三步优化后,我的单步求解时间从原来的20多秒降到了5秒左右,整个96时段的滚动调度只要不到8分钟就能跑完一天的全过程,这对仿真研究来说已经完全够用了。
5.4 代码泛化到实际工程场景要改什么
如果你不是做仿真课题,而是想把这套逻辑迁移到实际项目里,有几个点需要额外关注。
首先是网络拓扑。IEEE 33节点是纯辐射状结构,但实际配电网可能存在闭环运行或含分布式电源的情况,前推回代法就不适用了,需要换成牛拉法或PQ分解法。代码架构上,compute_pf_forward_backward.m这个函数是独立封装的,替换成新的潮流计算方法后,上层优化模型不受影响。
其次是分布式电源的接入。如果配电网里有光伏或者风电,节点注入功率就不再是纯负荷,而是"负荷减发电"。这种场景需要在滚动优化中把DG的预测出力序列加入节点功率平衡方程,并把DG的无功调节能力也纳入调度变量。
最重要的是电池寿命成本。我在基础模型里没有加入电池充放电老化约束,结果调度出的V2G策略会让部分车辆频繁切换充放状态,这对电池的实际损害很大。真正做工程落地时,建议在目标函数里加入充放电循环惩罚项,或者限制每台车一天之内的放电次数。这也是我这套代码下一步准备扩展的方向。
最后说一点个人体会。这套代码最初是我为了做课题对比写的,后来发现真正难的不是把优化模型搭出来,而是让模型在滚动优化框架下稳定"跑得动、解得快、结果合理"。我在调试时吃过最大的亏就是一次性把约束加到最严,结果Infeasible信息淹没在复杂模型里很难定位。建议读者拿到代码后先跑通无序充电基准场景,再逐步加入放电约束,每一步对比网损变化,这样一旦出错立刻知道是哪块引起的。电池循环寿命也值得尽早加入目标函数,否则V2G优化出来的策略可能对车主不太友好。希望这些经验能帮你少走点弯路。