先把丑话说在前面,如果你只是想把潮流跑通、把结果画出来,其实不需要二阶锥。但如果你想做的是“配电网日前优化调度”,也就是让Wind、CB、SVG、OLTC、ESS这些设备在24小时里协同出力,那就必须用一个能嵌入优化器的潮流模型,再交给CPLEX这种商用求解器去解。这也是我后来做MATLAB代码时,最后稳定下来的一套组合:DistFlow形式+二阶锥规划(SOCP)松弛+YALMIP建模+CPLEX求解。
这套流程解决的核心问题很接地气:光伏、风电这类分布式电源接入后,节点电压可能越限,储能怎么充放电、电容器组怎么投、有载调压变压器怎么调档、SVG怎么发无功,才能让全天网损最小、电压最稳、动作次数还不至于弄坏设备。它适合两类人看,一类是电力系统方向的研究生,要复现“考虑多种调控手段的多时段优化调度”论文;另一类是做主动配电网、微电网调度平台的工程师,想把日前计划从经验试算换成可解释的数学规划模型。下面我不会把整份代码贴出来糊弄人,而是会把建模思路、代码骨架、踩过的坑讲清楚,这样你拿回去之后能自己搭。
1. 先把模型对象放对位置:这五类设备谁调有功、谁调无功、谁调电压
很多人拿到这类题目就开始写约束,结果写到一半发现设备之间的时间尺度、离散程度完全不同,根本塞不进同一个模型。我建议动手前先做一次“设备体检”。
1.1 24小时调度问题到底长什么样:从根节点到末端的一条完整链路
先画一个典型场景:一条配网馈线从上级变电站引下来,根节点可能是变电站母线,也可以带OLTC;沿线若干负荷节点,其中某些节点接了风电、储能、SVG或者并联电容器组。这里的CB别理解成断路器,它是Capacitor Bank,也就是并联电容器组,这个缩写坑过不少人,拿到旧代码时务必先改名为CapBank之类的清楚命名。
这个调度问题本质上是在做“日前最优潮流”。风电的预测出力曲线通常是给定的,负荷曲线也是给定的,决策者要回答的问题是:在接下来24小时,每个时段OLTC档位放到几档,电容器组投哪几组,SVG发出多大的无功,储能是充电、放电还是待机,必要时风电要不要弃一部分。目标可以是全天购电成本最小、网损最小,或者二者折中。
这句话看起来简单,但背后是好几层约束:每个时段的潮流要满足物理定律,储能SOC要从上一个时段递推到下一个时段,OLTC和CB有动作次数限制,电压不能越限。一个变量动了,可能牵动一整天的计划,所以不能逐小时孤立地优化,必须一起算。
1.2 各自的“动作风格”完全不同,这决定了约束不能照抄
这里我用一张表把五类设备拆开看,写模型前先想清楚对方是连续量还是离散量:
| 设备 | 主要调节量 | 离散/连续 | 响应时间尺度 | 必须考虑的限制 |
|---|---|---|---|---|
| Wind(风机) | 有功出力、无功出力 | 连续(通常按预测上限削减) | 分钟级 | 弃风惩罚、无功容量随有功变化 |
| CB(并联电容器组) | 无功功率档位 | 离散 | 分钟到小时级 | 单组容量固定、动作次数有限 |
| SVG(静止无功发生器) | 无功出力 | 连续 | 毫秒级 | 容性/感性容量边界 |
| OLTC(有载调压变压器) | 电压档位 | 离散 | 很慢 | 档位步长、日动作次数限制 |
| ESS(储能系统) | 有功充放电 | 连续 | 秒级 | SOC上下限、充放电效率、不能同时充放 |
这五类设备的时间常数差异很大。OLTC和CB是“慢设备”,频繁动作会磨损有载开关和电容器投切开关,所以模型里通常要限制一天里的总动作次数,或者在目标函数里加动作惩罚。SVG和ESS是“快设备”,但ESS的调节能力受SOC约束,不是说想放多少电就能放多少。
如果没意识到这些差异,最简单的问题就会冒出来:目标函数只写“网损最小”时,求解器会把OLTC在一个小时内调十几次,因为每调一档都能降低一点损耗。这在数学上可行,工程上完全不可接受。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. DistFlow二阶锥松弛这套算法,到底把什么“非凸问题”给躲过去了
聊代码以前,这部分值得多花点篇幅。因为只要理解透了,后面很多调试都是顺理成章的。
2.1 牛拉法能算潮流,为什么不适合直接嵌进优化器里
最早我拿到这个需求时,第一反应是用Matpower算潮流,再把OLTC档位、CB投切状态用启发式算法一层层套上去。结果很痛苦:每算一次目标函数就要跑一次潮流,24时段就是24次潮流,还要叠加设备组合。像CB三组设备加OLTC九档这种规模,组合数是写不完的;就算用粒子群,最后给的方案你也无法证明它是全局最优的还是运气好碰到的。
数学规划的思路不一样,它把潮流方程作为约束放进模型,让求解器在可行域里同时找所有决策变量的最优值。问题在于,潮流方程里带有电压乘电流、功率平方除以电压这类非线性项,整个问题是非凸的。非凸问题用数学规划求解,运气好能找到局部最优,运气差连收敛都难。这时候DistFlow就出现了。
2.2 从两条功率平衡式到一条锥不等式,中间发生了什么
DistFlow是配电网辐射状结构下最常用的支路潮流模型。对每条支路,它有四个基本变量:首端有功P、首端无功Q、首端电压幅值平方V,和支路电流幅值平方L。
经典的四行方程大致是:上游流向这个节点的有功等于本站负荷和所有下游支路有功需求之和;无功方向同理;节点电压平方从首端往末端逐级降低,降低量里含有线路电阻、电抗和电流平方项;最后还有一个支路首端功率平方和等于电压乘以电流平方的关系。
如果直接用最后一个关系式来表达,里面会出现“电流平方等于功率平方除以电压平方”这类分式非线性项,非常难处理。DistFlow的巧妙之处是把电流幅值平方L提出来当作独立变量,然后把那个等号关系放宽松,写成不等式:
L ≥ (P²+Q²)/V
这个不等式本质上是一个旋转二阶锥,也就是一个凸集合。再通过简单变形,比如定义向量x,让它的范数小于等于V+L,就变成了CPLEX可以直接吃进去的标准二阶锥约束。用一句话总结:原本是一段非凸曲线上的点,现在放宽成锥形区域里的点,锥是凸的,所以全局最优可以求。
2.3 松弛紧不紧,是这个模型可信度的核心问题
放宽之后必须问一个问题:我们放宽出来的解,最后真的落在原来那条曲线上吗?如果最优解落在锥内部,说明这个可行解实际物理上根本不成立,也就是“松弛不紧”。
这个问题值得认真检查。我的经验做法是,跑完优化后,把每条支路的L×V和(P²+Q²)逐时段对比,算一下最大残差。残差在1e-4以内通常认为可行;如果这个数到了1e-2量级,说明这个松弛结果不可信,必须回头改目标或者约束。
那么什么情况下理论上有保障?如果在目标函数里对支路电流或者网损有正向的惩罚,那么求解器会倾向于压低L,这时候锥松弛比较容易被压到边界上,结果是紧的。反过来,如果目标函数太怪,某些支路在目标里权重为零甚至负权重,就可能出现求解器故意钻锥内部空子的问题。所以实操时只靠理论不够,每次运行完都得验。
3. MATLAB+YALMIP+CPLEX落地:建模代码的关键框架与常见误区
代码写得好不好,第一步不在于语法流不流畅,而在于是不是一开始就有一个清晰的变量表。我复现这类模型时,习惯把所有决策变量先按类别铺开,再写约束。
3.1 变量声明:先用sdpvar还是binvar,心里得有谱
拿一个简化版的33节点配电网系统举例,支路数nl、节点数nb、时段T=24。你需要声明的变量至少包含这些:
| 变量类别 | 具体变量 | MATLAB中写法 |
|---|---|---|
| 网络量 | 支路有功、无功、电流平方 | P = sdpvar(nl,T,'full') 等 |
| 节点电压 | 电压幅值平方 | V2 = sdpvar(nb,T,'full') |
| 储能 | SOC、充电功率、放电功率 | soc、pCh、pDis 各为 1×T |
| CB投切 | 电容器组状态 | binvar(T, numCB) |
| OLTC档位 | 若干档位二进制变量 | binvar(T,numTap) 或按档位编号 |
这里有个很常见的小坑:sdpvar默认创建的是对称方阵,如果你要的是“节点×时段”的矩阵,必须加'full'这一参数。不熟悉的人经常会写漏,然后发现约束矩阵维度永远对不上,排查半天都找不到原因。
声明变量后,下一步是把每个节点上接入的设备写清楚。风电通常给预测最大出力,实际出力可以在0到预测值之间调,差额记为弃风;CB的无功是档位乘以单组容量;SVG就是一个带上下界的连续无功变量;ESS用充电功率和放电功率两个非负变量来表示,避免直接用带符号变量带来效率式子不清晰。
3.2 DistFlow约束怎么写到YALMIP里:最好直接用cone而不是手写平方约束
网上很多代码会把二阶锥约束直接写成V2*L >= P^2+Q^2,这对YALMIP来说有时候可以识别成旋转锥,但不同版本之间行为不一致,而且还可能因为变量排列问题把求解器绕晕。我自己更推荐显式地用cone函数写成标准二阶锥形式,让求解器收到它最熟悉的格式。
下面是一个单时段的骨架示例:
matlab复制% 先给变量
V2 = sdpvar(nb,1); % 节点电压幅值平方
P = sdpvar(nl,1); % 支路有功
Q = sdpvar(nl,1); % 支路无功
L = sdpvar(nl,1); % 支路电流幅值平方
Constraints = [];
for k = 1:nl
f = branch(k,1); % 首端节点
t = branch(k,2); % 末端节点
r = branch(k,3);
x = branch(k,4);
% 二阶锥约束等价于 V*L >= P^2 + Q^2
Constraints = [Constraints, ...
cone([2*P(k); 2*Q(k); V2(f)-L(k)], V2(f)+L(k))];
% 电压降落方程
Constraints = [Constraints, ...
V2(t) == V2(f) - 2*(r*P(k) + x*Q(k)) + (r^2 + x^2)*L(k)];
end
注意DistFlow里的电压降落方程中,前面系数是2,最后一项的等值阻抗是r²+x²,这个组合我刚写的时候经常背错。还有一个方向性问题:这里from/to的顺序是从根节点往末端指向,这样定义P、Q才是支路首端向末端流动的正向功率。如果from/to方向反了,整份潮流约束就全错了,而且很难查出来。
3.3 OLTC和CB这类离散设备,怎么接入才能不破坏SOCP结构
OLTC看起来只是加一个变压器支路,但把档位变量加进去后很容易引入两个变量相乘的双线性项,处理不好会破坏整个SOCP结构,甚至在YALMIP里直接出现非凸告警。
我的建议按这步走:把OLTC所在的支路拆成“理想变压器+线路阻抗”两段。理想变压器的特点是,它只改变电压幅值、不产生有功和无功损耗。当变比离散为若干个档位时,电压平方之间要满足“v末端 = 变比平方 × v首端”的关系,其中变比平方是可选的离散值。
问题是离散变比和连续电压相乘,仍然是双线性。工程上常用大M法把每个档位对应的乘积线性化,思路是引入辅助连续变量和一个二进制变量,用一对“电压上下界×档位状态”的大M约束去夹住辅助变量,使辅助变量在档位被选中时等于电压值、没被选中时等于零。这样模型最终会变成一个混合整数二阶锥规划(MISOCP),CPLEX同样可以处理,只是离散变量多时求解会慢一些。
如果你把OLTC放在根节点变电站出口,另一个更省事的替代做法是把根节点电压本身的允许值限制在一组离散平方值里,比如0.95²、0.975²、1.0²、1.025²、1.05²这五档,这样就不涉及双线性项了。到底用哪种,取决于你要仿真的网架结构。
并联电容器组相对简单:每组容量是固定常数,要么投入要么不投入,状态用二进制变量,总无功等于各状态乘以单组容量。需要动作次数限制时,再引入相邻时段状态差值的绝对值。
3.4 求解器调用:CPLEX不是傻瓜相机,参数得自己盯
YALMIP负责建模化简,真正干重活的还是CPLEX。求解调用长这样:
matlab复制ops = sdpsettings('solver','cplex','verbose',2);
ops.cplex.mip.tolerances.mipgap = 1e-4;
ops.cplex.mip.tolerances.absmipgap = 1e-4;
sol = optimize(Constraints, Objective, ops);
if sol.problem ~= 0
disp(sol.info);
else
disp('求解成功');
end
对纯连续的二阶锥规划,CPLEX走barrier算法,精度很容易满足。但模型里一旦有OLTC和CB的整数变量,就变成MISOCP,这时候一定要设置一个合适的mipgap。把mipgap设成1e-6,求解时间可能毫无必要的变成十分钟;设成1e-3,工程上通常够用,但论文里复盘结果时又容易被审稿人质疑。我一般取1e-4,速度和精度比较平衡。
还要提醒一点,CPLEX参数名在不同版本之间偶尔有变化。如果你在YALMIP里写了ops.cplex.xxx但没生效,YALMIP很可能把警告吞掉了,这时候可以暂时把verbose开到3,看CPLEX自己的日志到底识别了哪些参数。
4. 24小时多时段协同:目标函数与时序约束这场“全盘博弈”
4.1 为什么必须做成24小时整体优化,而不是逐小时解
如果不考虑储能,逐小时解也无所谓,因为每个时段相互独立。但储能SOC天然跨小时耦合,今天早上充的电,下午才能放。OLTC和CB动作次数也得跨时段统计,今天调一次、明天少调一次,这种约束不是单时段模型能表达的。
所以多时段模型的时间维度
