☰
断线解环:配电网辐射状拓扑约束的MILP建模与Matlab实现
2026/10/10 7:07:06 网站建设 项目流程

做配电网重构、有源配电网规划、故障恢复这些方向的人,绕不开一个看起来简单但特别容易翻车的约束:辐射状拓扑。我最近把一篇EI论文里的“断线解环”约束建模方法完整复现了一遍,用Matlab写了一套自动生成基本环矩阵、加拓扑约束、调MILP求解的流程。这个思路真正跑通之后,你会觉得以前用的生成树约束、虚拟潮流约束都绕远路了。这篇文章适合正在做配电网重构、开关优化、故障转供,或者被“无环+连通”折腾得睡不着觉的同学。下面我直接把建模原理、Matlab代码框架、算例结果和踩坑经验都摊开讲。

1. 配电网辐射状约束为什么难建,断线解环思路从哪来

1.1 闭环设计、开环运行:配网的安全底线

配电网和输电网最大的区别,就是输电网可以放心地环网运行,而配电网绝大多数情况下必须保持辐射状。所谓辐射状,就是从变电站这个电源出发,每一条馈线像树杈一样向负荷供电路径,中间没有闭合回路。配电网在规划阶段常常把联络开关布置好,形成“闭环设计、开环运行”的结构:平时联络开关全部打开,网络是放射状;某个区段故障时,调度员把联络开关合上,再把故障附近的开关断开,负荷从其他馈线转带。

为什么要坚持开环运行?首先是继电保护配合简单。配网保护没有输电网那么复杂的纵联差动,大多靠电流保护和重合闸。如果网络出现环路,短路电流分布和潮流方向都会变得不确定,保护定值很难配合。其次是短路电流控制,环网状态下馈线短路电流会被多条路径分担,导致部分开关的短路电流水平超限。再一个就是运维习惯,树状拓扑从变电站到任何一个负荷点只有一条路,调度员判断供电路径非常直观。

但问题是,“辐射状”在数学上并不好写成一个干净的约束。它要求网络同时满足三个条件:连通、无环、支路数等于节点数减一。这三个条件里任意两个组合起来都能推出辐射状,但它们全和0-1拓扑变量搅在一起,直接放进优化模型很容易变成非线性或大规模组合约束。

1.2 传统辐射状约束的三种常见套路

我在复现论文之前,先梳理过目前主流的辐射状约束建模方式,大概可以分成三派。

第一派是生成树约束。思路是让每个节点都找到一条通往根节点的唯一路径。实现方式可以是给每个节点强制规定一个父节点,根节点除外,然后再加上防环的排序约束。这一类约束逻辑严密,但要么引入大量辅助变量,要么需要额外保证“父节点链不能形成环”,在Matlab里组装矩阵时很容易出错。

第二派是虚拟潮流约束,也叫单商品流约束。做法是从根节点注入一个单位的虚拟流量,每个负荷节点消耗一个单位,每条支路只有在闭合时才能通过虚拟流量,用big-M把支路潮流和开关状态耦合起来。这种方法优点是连通性天然保证,缺点是它只保证“每个负荷点都能被访问”,并没有排除环。如果支路闭合数量不受控,四个节点四条边也可以让虚拟流从根节点出来绕一圈再回到某个节点,照样满足所有流量平衡。所以虚拟潮流约束通常还得配合支路计数约束才能用。

第三派是割平面迭代。先不管环,求解之后检测拓扑里是否有子环路,有就加一条割断子环的不等式,反复迭代直到无环。这种方案在中小规模系统里很稳,但每迭代一次就要重新求解一次MILP,随着网络规模变大,迭代次数可能失控。

这三派都有道理,但都有一个共同的问题:模型表达和工程人员的实际调度习惯脱节。调度员恢复供电时就是一句话——合上联络开关,再找一个合适的开关断开,也就是“断线解环”。那为什么不直接把这个习惯写成约束呢?这恰恰是断线解环建模方法的出发点。

1.3 断线解环:调度操作里长出来的建模方法

断线解环思想的底层逻辑非常直观:把一个由分段开关和联络开关构成的配电网中所有开关全部合上,网络会形成若干个独立环。独立环的数量可以用图论算出来,等于支路数减节点数加一,通常就是联络开关的数量。要想让网络重新变成辐射状,就必须在每个独立环里至少断开一条支路,而且断开的支路总数必须等于独立环数量。

这个思路把一个全局的“连通+无环”问题,转换成了“每个基本环至少断一条线”的局部约束。基本环可以预先通过图论算法自动生成,不需要在求解过程中反复迭代。生成的约束全部是0-1线性不等式,直接扔进intlinprog、YALMIP+Gurobi、CPLEX这些求解器都能跑。而且物理意义极其清晰,我写代码时能直接拿着结果对调度员解释:这张网为什么合某个联络开关、断某条分段开关是合理的。

下面我把数学建模部分拆开,讲清楚两条核心约束到底是怎么来的。

2. 断线解环数学建模:两个线性约束锁定辐射状拓扑

2.1 变量定义与网络基本环

在开始建模之前,先约定网络描述。设配电网连通图 (G=(V,E)),节点集合 (V) 的个数为 (N),把分段开关和联络开关全部只保留“可开断”属性后,支路集合 (E) 的个数为 (M)。把所有开关状态都设为闭合时,网络是一个连通图,它的独立环数量为:

[ L = M - N + 1 ]

这里的 (L) 就是常说的基本环数,在单变电站供电的配电网里,它一般等于联络开关数。如果遇到多变电站多电源网络,可以先把变电站节点合并成一个虚拟根节点,再按同样的方式生成基本环。

定义每个支路 (e) 上的0-1断线变量:

[ z_e = \begin{cases} 1, & \text{支路 }e\text{ 断开}\ 0, & \text{支路 }e\text{ 闭合} \end{cases} ]

对于不允许操作或固定闭合的支路,直接令 (z_e=0)。闭合变量就是 (x_e=1-z_e)。有了这些定义,就可以写约束了。

2.2 核心约束:每个环至少断开一条,总数等于环数

断线解环的辐射状约束一共两条。

第一条约束要求网络中所有断开支路的数量等于基本环数量:

[ \sum_{e=1}^{M} z_e = L ]

因为全闭合网络总共有 (M) 条支路,断开 (L) 条之后,剩余闭合支路数为 (M-L=N-1)。这正好是树结构要求的支路数。

第二条约束要求每个基本环内部至少有一条断开支路。把第 (l) 个基本环包含的支路集合记为 (\Omega_l):

[ \sum_{e\in \Omega_l} z_e \ge 1, \quad l=1,2,\dots,L ]

这两条加在一起,效果就很完整了。第一条约束保证了最后网络的总支路数不多不少,第二条约束保证了所有基本环都被“打破”。对于一张连通图来说,没有环,而且支路数等于节点数减一,那就只能是连通树,也就是辐射状网络。

这里有一个特别容易忽略的细节:第一条总数约束不能省。假如只写“每个环至少断一条”,求解器可以把一条支路同时算作两个环的“断线”,结果只断开 (L-1) 条线,剩余支路数变成 (N),仍然可能有一个环,网络并不是辐射状。反过来,只写总数约束不写每个环至少断一条,求解器可能把所有断线都集中在同一个环里,其他环完好无损。所以两条约束必须同时存在,缺一条都不行。

2.3 为什么这两条约束能替代连通性条件

很多人第一次看到这个模型会问:断线解环只保证无环,连通性谁保证?

答案在于图论里一个基本结论:对于一个 (N) 个节点的无环图,如果边数正好是 (N-1),那么这个图一定是连通的。断线解环约束确实没有直接写连通性,但“无环”和“总边数等于N-1”这两个条件合在一起,已经隐式地把连通性锁死了。如果网络断开成两个互不相连的子图,那么两个子图各自无环时,总边数最多只有N-2,达不到N-1。所以只要约束被完整满足,就既无孤岛,也无环路。

我在复现初期其实对这一点不放心,每次都额外用Matlab的conncomp检查一次连通性。测了十几个算例之后发现,只要两条约束都写上,连通性检查永远都是通过的。当然,如果用的是非线性或者启发式求解,最终解未必严格满足MILP约束,那时候仍需要单独校验。

3. Matlab复现流程:自动生成基本环矩阵并加约束

3.1 输入数据准备

Matlab复现第一步是准备支路数据。我以IEEE 33节点系统为例,这个系统有33个节点、37条支路,其中5条是联络支路,初始断开。把支路数据整理成矩阵,每一行大致是:

% branch = [首端节点 末端节点 电阻 电抗 容量 初始状态] % 状态1表示常闭,0表示联络开关初始打开 branch = [ 1 2 0.0922 0.0470 0 1; 2 3 0.4930 0.2511 0 1; ... 8 21 0.5242 0.3177 0 0; % 联络开关 9 15 0.5242 0.3177 0 0; ... ];

需要提醒的是,Matlab的节点编号最好从1开始连续编号。如果原始数据里有节点0,宁可麻烦一点把编号平移成1到N,不然后续graph、conncomp、generateLoop这些操作都容易出边界问题。

初始状态只影响初始潮流,不影响拓扑约束建模。因为断线解环的思想是“先认为所有开关都能闭合,形成基本环,再从中选出一组断线”。所以不管联络开关初始状态是什么,建拓扑约束时都要用“全闭合网络”来生成基本环。

3.2 基本环矩阵生成的Matlab实现

基本环矩阵是这套建模方法的核心。矩阵的每一行代表一个基本环,每一列对应一条支路,元素为1表示这条支路在这个环里。生成方法很固定:先对全闭合网络做一次BFS或Kruskal生成生成树,然后每遇到一条不在生成树里的非树边,就把这条非树边加上树里两个节点之间的唯一路径,组合成一个基本环。

以下是一个我一直在用的Matlab生成函数:

function loopMat = buildLoopMatrix(branch, N) % branch: M x 2 矩阵,第一列首节点,第二列末节点 % N: 节点数 M = size(branch,1); % 边编号矩阵,方便根据节点对查支路编号 edgeId = zeros(N,N); for k = 1:M i = branch(k,1); j = branch(k,2); edgeId(i,j) = k; edgeId(j,i) = k; end % BFS生成生成树 treeEdge = false(M,1); parent = zeros(N,1); visited = false(N,1); queue = 1; visited(1) = true; parent(1) = -1; while ~isempty(queue) cur = queue(1); queue(1) = []; nb = find(edgeId(cur,:) > 0); for j = nb if ~visited(j) visited(j) = true; parent(j) = cur; treeEdge(edgeId(cur,j)) = true; queue(end+1) = j; end end end % 对每条非树边生成一个基本环 loopMat = zeros(0,M); for k = 1:M if ~treeEdge(k) u = branch(k,1); v = branch(k,2); p1 = pathToRoot(parent, edgeId, u, M); p2 = pathToRoot(parent, edgeId, v, M); lp = xor(p1, p2); % 树中u到v的唯一路径 lp(k) = 1; % 加上非树边 loopMat(end+1,:) = lp; end end end function pe = pathToRoot(parent, edgeId, node, M) pe = false(1, M); while node > 1 && parent(node) > 0 p = parent(node); e = edgeId(p, node); pe(e) = true; node = p; end end

这个函数有几个隐蔽的坑。第一,BFS必须从某个节点开始遍历全图,如果遍历结束之后visited里还有false,说明全闭合网络不连通,程序应该直接报错,不要往下跑。第二,pathToRoot里用的是节点“向上走到根”的路径,路径上边的编号通过edgeId矩阵反查,如果两个节点在同一条路径上,xor会把公共段抵消,得到的就是它们之间的唯一树路径。第三,非树边一定要最后再置1,因为前两条路径都用的是生成树里的边,不能把非树边提前混进去。

3.3 约束装配与求解框架

拿到loopMat之后,拓扑约束的装配就非常机械了。我用YALMIP写起来最直观:

nLoop = size(loopMat, 1); M = size(branch, 1); % 断线变量 z,1表示断开 z = binvar(M, 1); % 每个基本环至少有一条断线 Constraints = [loopMat * z >= 1]; % 总断线数等于基本环数 Constraints = [Constraints, sum(z) == nLoop]; % 固定闭合的支路不允许断开 fixedClosed = find(branch(:,6) == 1); % 按你的数据列定义 Constraints = [Constraints, z(fixedClosed) == 0];

如果不想用YALMIP,用纯intlinprog也可以实现同样约束。把loopMat拆成不等式矩阵:

A = -loopMat; % loopMat*z >= 1 => -loopMat*z <= -1 b = -ones(nLoop,1); % 总数约束:sum(z) == nLoop Aeq = [ones(1,M)]; beq = nLoop; lb = zeros(M,1); ub = ones(M,1); intcon = 1:M;

目标函数要根据实际场景定。如果只验证拓扑可行性,f取全零向量就行;如果做重构,需要把网损或开关动作次数目标加进来,同时补上DistFlow潮流约束和电压电流上下限。我建议的求解器顺序是:有Gurobi或CPLEX许可证就优先用它们,没有就用Matlab自带的intlinprog,33节点及以下规模都能轻松求解,几百节点的网络只要big-M给得合适,也能在可接受时间内收敛。

4. 算例验证:IEEE 33节点重构效果与结果分析

4.1 算例设置与结果表

我用IEEE 33节点标准参数做了验证。全闭合网络有37条支路、33个节点,基本环数:

[ L = 37 - 33 + 1 = 5 ]

所以建模后约束就是:5条断开支路,且5个基本环里每个至少断一条。目标函数用系统网损最小,潮流采用DistFlow线性化近似。求解完成后,我把结果和初始状态放在一起对比。

对比项初始网络断线解环重构后
断开支路联络开关33、34、35、36、37全开7、9、14、32、37
闭合支路数3232
基本环断线数量—5
系统网损202.68 kW139.55 kW
最低节点电压0.9131 p.u.0.9378 p.u.

不同文献里IEEE 33节点联络开关编号顺序不完全一致,所以断开支路的编号可能对不上,这不重要,关键是结果指标:网损降低了大约31%,最低电压抬升了大约0.025 p.u.,而且断开支路数量正好等于5条,最终网络通过Matlab的graph连通性校验。这说明断线解环约束没有把最优解丢掉,也没有放进来一个非辐射状解。

4.2 对结果的理解:约束没有丢解也没误伤

我一开始担心断线解环这种“先闭合再断开”的建模方式会不会漏掉一些本来不需要闭合联络开关的方案。后来想明白了一个点:网络重构本来就是通过改变分段开关和联络开关状态来寻找最优树。断线解环的全闭合网络只是用来生成基本环集合和计算独立环数量的一个中间图,最终优化模型里的可断开支路集合包含了所有候选开关。因此,任何一个最终辐射状树,都可以看作是从全闭合图里断开了 (L) 条边得到的,一定能够在解空间中被表达出来。换句话说,断线解环约束没有增加额外限制,它只是把树结构的“生成”换了一种等价表达。

另外,约束是纯线性的,没有迭代割平面,求解器拿到的就是全局最优或至少是给定MIP gap下的最优。相比启发式算法,这个优势在需要反复评估大量场景时非常明显。

5. 常见问题与排查技巧实录

5.1 典型问题速查表

复现过程中我遇到过不少问题,大部分集中在基本环生成和约束不可行上,下面整理成速查表。

现象可能原因处理办法
buildLoopMatrix报错或loopMat行数不对节点编号不连续,或者全闭合网络不连通检查节点编号1到N连续;BFS遍历后检查visited是否全部为true
约束不可行某个基本环内所有支路都被固定为闭合,没有可断开支路检查fixedClosed集合;环内至少保留一条可动支路
求解结果有孤岛只写了每个环至少断一条,忘了写总数约束补上sum(z) == nLoop,再接conncomp校验
intlinprog求解很慢big-M给得过大,或没有设置求解精度把与潮流耦合的big-M压缩到全网总负荷的1.2倍左右;设置MIPGap
联络开关全闭合后基本环数等于0误把当前开环状态当作建模图一定用所有开关闭合后的图生成基本环
多环共享一条支路时最优解变得怪异约束没有限制总断线数,导致断线重叠总数约束回填后,再观察断开支路是否集中

5.2 从复现里踩出来的几条避坑经验

第一,约束装配顺序最好固定为:基本环不等式、总数等式、固定闭合约束。固定闭合约束不能放在总数等式之前单独处理,否则一旦某个固定闭合支路被某条基本环约束“选中”,求解器会永远报不可行。正确做法是先保留所有支路的断线变量,然后用等式把固定闭合支路的断线变量强制为0,让求解器在环内其他支路上寻找可行断线。

第二,big-M的取值很关键。如果潮流约束要跟开关状态耦合,big-M不是越大越好。我最初把big-M设成1e6,结果求解器在分支定界时数值稳定性差,得到很多奇怪的小数断线状态。后来把big-M压到全网总负荷的1.2倍,并把电压平方项做了归一化,问题立刻清爽了。经验是:big-M只要能保证支路功率在闭合时的可行范围上界足够,就不要取得过大。

第三,一定要在求解后做一次“双保险”校验。用Matlab的graph对象把断开支路删掉,再检查边数和连通分量数,这是一个写起来不到三行、但能救回无数次错误结果的步骤。实际做法:

openIdx = find(z_val > 0.5); G = graph(branch(:,1), branch(:,2), [], N); G = rmedge(G, openIdx); if numedges(G) == N-1 && numel(unique(conncomp(G))) == 1 disp('radial topology: OK'); else error('topology check failed'); end

我自己在复现这套方法时踩过最大的坑,就是有一版代码漏了总数约束,解出来表面上看每个环都被打破了,但网络实际被分成了两片。后来把sum(z)==nLoop加上,所有结果都恢复正常。断线解环看起来简单,真正落地时核心全在“基本环生成”和“两条约束都要加上”这两件事上。如果你也在复现类似方法,我的建议是先跑通纯拓扑校验,再往里面加潮流目标,这样一旦结果不对,你能很快定位是拓扑的问题还是潮流的问题。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询