简介:这套资源面向电力系统优化调度方向的研究者与工程师,聚焦含风电、无功补偿CB、静止无功发生器SVG及有载调压变压器OLTC等设备的主动配电网动态最优潮流问题。不同于传统潮流求解,资源采用二阶锥规划(SOCP)建立凸优化模型,借助MATLAB+YALMIP+CPLEX实现高效求解,代码提供讲解视频便于快速上手。压缩包共23个文件,以两个MATLAB源码文件为核心,辅以SOCP-OPF复现过程文档、潮流计算PPT、IEEE33节点网络图、相关PDF与CAJ文献及运行日志,整体大小约117.18MB,便于对照研究。目前已有843人学习下载,其中店主编写的复现全过程参考文档详细拆解建模到求解环节,配合经典文献和仿真代码,可帮助读者深入掌握主动配电网二阶锥优化的工程实现方法。
1. 为什么主动配电网调度要引入二阶锥规划
拿到一个名为“基于二阶锥规划的主动配电网优化调度代码”的项目,第一反应不是去看代码,而是先把问题定义清楚:主动配电网的优化调度,本质是在电压、电流、储能荷电状态的约束下,决定光伏/风电出力、储能充放电以及联络开关状态,使得网损或运行成本最小。这个问题直接建模是带二次等式约束的非凸优化,全局最优很难求。二阶锥规划(SOCP)通过变量替换和松弛,把非凸约束转成等价的锥约束,让主流求解器在可接受时间内给出全局最优解。下面顺着“为什么能用、怎么建模、代码怎么写、参数怎么调”这条线往下走。实际工程里,这个思路也常见于微网优化调度,区别只是主动配电网的节点规模更大、约束细节更多。
2. 二阶锥松弛的建模原理:从DistFlow到旋转锥
2.1 配电网潮流为什么不能直接丢给求解器
配电网调度最常用的潮流方程是DistFlow支路潮流模型。以辐射状网络的一条支路 i-j 为例,稳态下的关系是:
V_j^2 = V_i^2 - 2(r_ij P_ij + x_ij Q_ij) + (r_ij^2 + x_ij^2) I_ij^2
同时电流与功率、电压之间满足:
I_ij^2 = (P_ij^2 + Q_ij^2) / V_i^2
第二个等式把电流、电压、有功、无功耦合在一个非线性等式里,P_ij、Q_ij、V_i 又都是优化变量。如果原样写进模型,求解器会把它当作一般的非线性约束,得到的解大概率是局部最优。实际项目中我见过不少直接用 SQP 或 IPOPT 调度的案例,网络规模稍大就计算时间呈指数增长。
常见的处理方式是把变量换一下:令 U_i = V_i^2,L_ij = I_ij^2。代入后,支路电压递推变成线性等式,而电流功率关系变成旋转锥约束 L_ij * U_i ≥ P_ij^2 + Q_ij^2。注意原约束是等式,这里放宽成不等式。之所以可以放宽,是因为目标函数通常包含网损项,而网损正是 L_ij 的线性函数。越小的 L 对目标越有利,所以最优解会自动落在这个锥的边界上,也就是回到等式。
表 2-1 给出这些变量在代码里的常用名称。
| 变量 | 物理含义 | 代码中的名字 |
|---|---|---|
| P_ij | 支路首端有功 | Pij |
| Q_ij | 支路首端无功 | Qij |
| U_i | 节点电压幅值的平方 | U |
| L_ij | 支路电流幅值的平方 | L |
| r_ij / x_ij | 支路电阻 / 电抗 | branch(:,3:4) |
2.2 把旋转锥改写成标准二阶锥
Gurobi 和 MOSEK 的 MATLAB 接口通常接受标准二阶锥形式:||x||_2 ≤ t。旋转锥 L*U ≥ P^2+Q^2 需要改写成:
|| [2P; 2Q; U - L] ||_2 ≤ U + L
从数值稳定性角度,这种标准形式的求解器预处理效果最好。在 YALMIP 里可以直接用cone指令,不要自己再去展开。
% 每个时段、每条支路定义一次锥约束 % x 是向量 [2*P; 2*Q; U_i - L],t 是上界 U_i + L Constraints = [Constraints, ... cone([2*Pij(k,t); 2*Qij(k,t); U(i,t) - L(k,t)], ... U(i,t) + L(k,t))];这里第一个参数是 3 维向量,第二个参数是标量,含义就是第一个参数的二范数不超过第二个参数。注意 U + L 在求解过程中始终大于等于零,不会出现上界为负的问题。
2.3 为什么是 SOCP 而不是 SDP
有人会问,为什么不直接用半定规划松弛。配电网的 SOCP 松弛在数学上是 SDP 松弛的特例,但求解效率差异明显。对带储能的长时间尺度调度模型,SDP 的矩阵变量会迅速拖垮内存;SOCP 只添加了几个标量辅助变量,所有约束都是线性的或锥的。实践中,SOCP 的求解时间比 SDP 少一个数量级,而松弛紧性在辐射状网络中已经足够。
这里有个容易踩的坑:不要直接把L * U == P^2 + Q^2写进约束,即使 YALMIP 能识别成非线性等式,也会丢掉凸结构,最终调用的还是非线性求解器。手动写成 cone 才能触发锥求解器。同时,支路电流限值、电压限值、储能充放电约束都是线性的,可以原样放入。
好了,到这里原理已经清楚。下一步就是要有一份可以改动、可以跑通的代码。
3. 用YALMIP在MATLAB里跑通SOCP调度:最小代码与参数
3.1 一个5节点系统的数据准备
常见做法是先用一个最小网络验证模型,我这里的例子是5节点辐射状配电网。节点1是上级变电站,能力视为足够大;节点3接光伏,节点4接储能,所有节点都带负荷。基准容量取10MVA,调度周期24小时,时间粒度1小时。
%% 数据输入 branch = [1 2 0.0922 0.0470; 2 3 0.0493 0.0251; 2 4 0.0820 0.0340; 4 5 0.0740 0.0370]; bus = [0 0; 50 30; 20 10; 40 20; 30 15]; % 支路第1、2列是首末端节点,第3、4列是r,x(标幺) % bus第1列是P_load(kW),第2列是Q_load(kVar) baseMVA = 10; % 基准容量,做单位归算用 bus(:,1:2) = bus(:,1:2) / (baseMVA*1000);这段代码的要点是:支路阻抗已经是标幺值,负荷需要用基准容量换算。很多代码包的出错点往往不是模型写错,而是负荷功率忘记转换,导致电压越限。表 3-1 是这里常用的变量清单。
| 变量名 | 维度 | 说明 |
|---|---|---|
| Pij, Qij | nl × nt | 每条支路每时段的有功、无功 |
| U | nb × nt | 每个节点每时段的电压平方 |
| L | nl × nt | 每条支路每时段的电流平方 |
| Pgen, Qgen | nb × nt | 节点注入功率,变电站和分布式电源均为非负 |
3.2 目标函数、潮流约束和锥约束
对上文提到的5节点系统,模型定义分三块。第一块是支路潮流约束和锥约束,第二块是节点功率平衡,第三块是储能系统约束。这里只列关键部分。
%% 约束定义 Constraints = []; % 支路潮流递推 + 二阶锥松弛 for k = 1:nl i = branch(k,1); j = branch(k,2); for t = 1:nt Constraints = [Constraints, ... U(j,t) == U(i,t) - 2*(branch(k,3)*Pij(k,t) + branch(k,4)*Qij(k,t)) ... + (branch(k,3)^2 + branch(k,4)^2)*L(k,t)]; Constraints = [Constraints, ... cone([2*Pij(k,t); 2*Qij(k,t); U(i,t)-L(k,t)], ... U(i,t)+L(k,t))]; end end % 节点功率平衡:注入 = 负荷 + 支路流出 % 这里用关联矩阵实现,代码略锥约束后面那行注释已经写了。这里解释一下第一条约束:它把节点 j 的电压平方和支路功率联系起来,是 DistFlow 一阶线性项,原模型的非线性耦合项已经由锥约束替代。注意 Pij 方向是从首端到末端,如果网络方向定义反了,锥约束不会报错,但结果会偏。
目标函数我一般写作购电成本加储能损耗,具体到分时电价场景,可以这样写:
%% 分时电价:0-5时为低谷,6-11为平时,12-17为高峰,18-21为平时,22-23为低谷 price = [0.48*ones(6,1); 0.58*ones(6,1); 0.82*ones(6,1); ... 0.58*ones(4,1); 0.48*ones(2,1)]; Objective = sum(price' .* Pgen(1,:));这里Pgen(1,:)是变电站节点的注入有功,price 转置成行向量后与它逐点相乘,反映全天购电费用。如果目标函数里同时希望最小化网损,可以让L的所有元素求和后乘以一个很小的权重加入目标,但权重不宜过大,否则会改变锥松弛的紧性。
3.3 求解器参数怎么设
模型写好之后,用optimize调求解器。我的经验是先固定solver,不要让它自动选,否则容易选到设置不合适的 SDPT3。
options = sdpsettings('solver','gurobi', ... 'verbose',2, ... 'gurobi.FeasibilityTol',1e-6, ... 'gurobi.OptimalityTol',1e-6); diagnostics = optimize(Constraints, Objective, options); if diagnostics.problem ~= 0 disp('求解失败,具体信息在 diagnostics.infostr 中'); endFeasibilityTol是原始可行域允许的最大误差,OptimalityTol是对偶间隙的收尾阈值。对电压平方这类标幺值在 1.0 左右的量,1e-6 的容差完全够用,没必要压到 1e-8 白白增加求解时间。
表 3-2 给出几种常见求解器的调用方式。
| 求解器 | YALMIP 名字 | 特点 |
|---|---|---|
| Gurobi | 'gurobi' | SOCP 求解快,商业授权但学术免费 |
| MOSEK | 'mosek' | 强项是二次规划和锥规划,数值稳定 |
| CPLEX | 'cplex' | 与 Gurobi 功能接近,默认参数偏保守 |
| SDPT3 | 'sdpt3' | 免费,但大规模会明显更慢 |
注意这里用的 YALMIP 会自动把目标函数识别为线性,配合锥约束后整个模型是凸的。如果求解日志里显示求解器切换到非线性求解器,就要回过去检查是不是有地方把L*U直接写成了等式。
4. 主动配电网优化调度代码包怎么读:数据、约束和求解器设置
4.1 解压后的文件结构和运行顺序
这类代码包很少是单文件。解压出来通常有一个主脚本,几个数据文件,一个结果绘图脚本。我的建议是先看文件命名,再按文件名从数据到模型到主脚本阅读。
| 文件/文件夹 | 作用 | 建议操作 |
|---|---|---|
| main.m | 入口 | 先运行,确认能跑通 |
| data/ | 保存系统数据 | 替换成自己拓扑 |
| yalmip_model.m | 定义变量与约束 | 改约束范围 |
| solve_opf.m | 求解和返回结果 | 调整求解器参数 |
| plot_result.m | 绘图 | 最后看 |
不要一上来就跑 main.m。先检查 main.m 里有没有addpath('yalmip')这样的路径设置。常见错误是 MATLAB 找不到 YALMIP,报Undefined function 'sdpvar'。解法是运行yalmipsetup,或者手动把 YALMIP 所在的目录加入 MATLAB 路径。
提示:修改网络参数后,先用
load('case33.mat','bus','branch')检查数据维度,再运行模型,可以省掉一半排错时间。
4.2 把固定网络参数换成自己的拓扑
最常见的修改点是网络数据。IEEE 33节点和实际10kV电网的支路长度、线径差别很大。修改的时候需要保持 branch 矩阵的格式:前两列是节点编号,第三四列是 r/x,第五列是长度。如果数据给的是每公里阻抗,需要乘长度。
% 从自己的CAD或GIS导出的网络参数:r/km, x/km, km branch_my = [1 2 0.23 0.12 0.35; 2 3 0.21 0.11 0.28]; branch_my(:,3) = branch_my(:,3) .* branch_my(:,5); branch_my(:,4) = branch_my(:,4) .* branch_my(:,5); branch_my(:,5) = [];这里把单位长度阻抗换算成总阻抗,然后再做标幺化。实际工程中,还要检查节点编号是否连续。如果节点编号跳号,矩阵索引会直接越界,求解器会返回Index exceeds array bounds。用assert可以快速发现这种问题。
4.3 常见报错和排错
| 报错信息 | 原因 | 处理方法 |
|---|---|---|
Undefined function 'sdpvar' | YALMIP 未安装或未加入路径 | 重新运行yalmipsetup |
Conic solver does not support this model | 有非凸约束没替换成锥 | 检查所有L*U是否写成 cone |
Infeasible problem | 可行域为空 | 检查储能初末 SOC 范围、联络开关约束 |
Termination due to numerical issues | 网络标幺基值不合理或容差过小 | 检查基值,放松容差到 1e-5 |
最常见的是违反辐射状假设。SOCP 松弛紧性依赖网络是辐射状,如果你的代码包里有联络开关,那就应该是混合整数二阶锥规划(MISOCP)。此时 YALMIP 需要调用binvar变量,求解器要允许整数,仍用 Gurobi 的 MISOCP 能力。如果直接去掉整数变量,优化结果会出现沿环线的功率分布异常,电压剖面也与实际不符。这类问题在代码包中非常隐蔽,因为 Gurobi 不会报错,只会给出一套看起来合理的数值。
5. 松弛紧性检验与大规模网络加速求解的实操技巧
5.1 求解后先检查锥残差
调度代码跑完后,不要直接看结果。我一般会检查每一个锥约束的紧性,因为只有紧的松弛才保证最优值等于原始非凸问题的下界。检查方法:求解后提取各个变量数值,计算每个支路每个时段下的残差。
Pv = value(Pij); Qv = value(Qij); Uv = value(U); Lv = value(L); residual = Uv(branch(:,1),:) .* Lv - (Pv.^2 + Qv.^2); if max(residual(:)) > 1e-4 warning('锥约束不紧,检查目标函数是否为网损的严格增函数'); end这个残差值理论上应该是 0,数值上一般小于 1e-6。如果大于 1e-3,说明目标函数里网损项的权重太低,或者节点电压已经顶到上边界,导致松弛有间隙。解决办法是增加一个小的网损惩罚项,例如在目标函数中加上1e-3 * sum(L(:)),把最优解重新拉回锥边界。
5.2 热启动与网络剪枝
对于几百个节点的网络,SOCP 规模很大。常见做法是分两步走:首先用 Gurobi 或 MOSEK 的默认容差求一个初解,然后把电压变量赋给 U 作为热启动,再设置更小的容差重新求解。YALMIP 里可以这样热启动:
assign(U, U_init); assign(L, L_init); options = sdpsettings('solver','gurobi','usex0',1);assign把初值赋给变量,usex0选项告知求解器使用外部初值。注意 Gurobi 对连续凸问题很少需要初值,但对 MISOCP 的整数变量初值有明显帮助。如果网络带联络开关,先用启发式方法固定开关状态求解连续 SOCP,得到一个较好的上界,再释放整数变量搜索,这就是工业界常见的热启动式调度。这种做法在我测试过的几十个节点网络中通常能快 20% 以上。
本文还有配套的精品资源,点击获取