1. 为什么“N-1”是配电网规划绕不开的坎
做配电网规划的人,几乎每天都和“N-1”打交道。这个词听起来像是某种密码,实际上它描述的是一种非常朴素的思路:电网里任何一个元件(线路、变压器、母线)突然故障退出运行,系统还能不能继续正常供电?如果可以,这个电网就通过了“N-1校核”;如果不行,那对不起,规划方案就得打回去重新做。
我在实际项目中见过太多类似的场景:新建一个工业园区,负荷预测做了,变电站选址也定了,但方案报上去之后,评审专家第一个问题就是“N-1过了没有”。很多刚入行的工程师会愣一下,心想“我容量算够了呀,为什么还要管故障状态”。这恰恰是配电网规划和普通输变电工程最大的区别——你不仅要考虑正常运行方式下的供电能力,还要考虑事故后是否会造成大面积停电。与其事后被专家问住,不如在做规划的时候就把N-1校核嵌进建模流程里,一次性搞定。
这次分享的“配电网N-1扩展规划Matlab实现”,本质上就是把“在现有网架基础上,新增线路或变电站,使得规划水平年下所有主变和线路都满足N-1”这个约束,变成一个可以求解的优化问题。它的应用场景非常明确:当负荷增长、原有网架供电能力不足时,我们需要决定“在哪里新建线路、新建哪些线路、什么时候投运”,同时保证投资最省、损耗最小、可靠性达标。
我最早接触这类问题是在一个县城配电网的技改项目里,当时靠手工试算,翻图纸翻到眼瞎。后来把N-1校验逻辑写成Matlab脚本,配合优化求解器,两天就把一版规划方案算出来了,而且每个N-1断面都有据可查。这篇文章就把这套方法的完整思路、数学模型、Matlab实现细节和踩坑经验全部梳理出来,希望能帮到正在做配电网规划、或者准备做相关毕业设计的同学。
2. 核心思路拆解:把“N-1校核”变成优化约束
2.1 从校验到规划的思维转变
先说一个很容易混淆的概念:N-1校核和N-1扩展规划是两件事。
N-1校核是“验证”:给定一个确定的网架结构,把每条线路逐一断开,看系统是否满足电压、潮流、容量约束。它的输出是“通过/不通过”,以及“哪些断面不通过”。这本质上是一个确定性的仿真问题。
N-1扩展规划是“决策”:在现有网架的基础上,给出一组可选的待建线路或变电站,我们要决定“选哪些、建在哪、什么时候建”,使得规划方案在N-1校验下全部通过,同时总费用最小。这是一个典型的组合优化问题,决策变量是0/1整数变量,表示某条线路是否新建。
这个思维转变非常重要。如果只会做校验,你只能在“给定方案”上打补丁;如果会做扩展规划,你才能在“方案形成阶段”就让N-1约束参与决策,从源头保证方案的可靠性。
我在项目里的经验是:传统做法是“规划-校验-返工”循环,通常要来回三轮以上;而扩展规划模型把校验逻辑内置到目标函数和约束中,一次求解就能给出满足N-1的优化方案,效率提升非常明显。
2.2 模型里到底放了哪些变量和约束
一个完整的配电网N-1扩展规划模型,至少要包含以下要素:
- 决策变量:待建线路的投建状态(0/1)、待建变电站的投建状态(0/1)、正常运行方式下各支路的潮流、N-1故障态下各支路的潮流。
- 目标函数:新建线路投资费用、新建变电站投资费用、运行网损费用,三者加权求和。
- 等式约束:正常运行方式下的节点功率平衡方程(KCL),N-1故障态下的节点功率平衡方程。
- 不等式约束:支路潮流上限(容量约束)、节点电压上下限、变压器容量约束、辐射状网络拓扑约束。
- N-1逻辑约束:这是最核心的部分——每对应一个故障场景,系统都必须存在可行的潮流解。
有些文章会把N-1约束写成“对于任意线路l开断,剩余网络连通且无过载”。在数学上,如果网络是辐射状的(配电网通常要求开环运行),那么N-1校验可以简化成“开断某条线路后,它所承载的负荷能否通过联络转供到其他馈线”。这种简化思路在纯辐射网里非常有用,因为它把复杂的潮流校验变成了“容量对比”。
但是,如果网络里存在环网结构(哪怕是正常运行时打开的联络开关),或者你考虑了电压约束,那就不能只做容量对比了,必须老老实实跑潮流。我建议初学阶段直接采用“故障场景潮流可行性”的建模方式,虽然计算量大一些,但结论更可靠,也方便后续扩展成N-1-1甚至N-k场景。
2.3 为什么选Matlab而不是其他工具
有人会问:配电网规划用专业的商业软件(比如CYME、DIgSILENT)不就行了?为什么还要用Matlab自己写?
我的回答是:商业软件适合做“方案验证”,但当你需要做“方案寻优”时,Matlab的灵活性是商业软件很难比的。具体来说,Matlab的优势有四个:
第一,Matlab的优化工具箱(Optimization Toolbox)和YALMIP、CVX等建模工具能非常方便地处理混合整数线性规划(MILP)问题。你只需要用类似人类思维的方式描述变量和约束,求解器(比如Gurobi、Cplex、intlinprog)会自动处理底层算法。
第二,Matlab的矩阵化编程风格和电力系统分析天然契合。节点导纳矩阵、潮流方程、灵敏度分析这些操作,在Matlab里写起来非常顺手。
第三,数据处理方便。配电网规划需要处理负荷数据、地理信息、设备参数表,Matlab的表格(table)、结构体(struct)和绘图功能让前后处理一体化。
第四,生态成熟。网上有大量基于Matlab的电力系统分析开源代码(比如Matpower),可以拿来即用,不用从零造轮子。
当然,Matlab的开销是商业授权,不过对于高校研究和工程设计来说,这通常是标配,不存在额外的工具门槛。
3. 数学模型:怎么写才算“严谨可求解”
3.1 基础假设与符号定义
在建立数学模型之前,先明确几个假设条件,这是保证模型可解性的关键:
- 规划水平年固定为某一目标年(比如2030年),不考虑多阶段动态规划,这样避免了时间维度的复杂度爆炸。
- 负荷采用峰值负荷,按典型日最大负荷计算,忽略时序波动。
- 待选线路的电气参数(电阻、电抗、容量)已知,且新建线路投产即为满参数运行。
- 变电站待选方案简化为“是否新建一台主变”,主变容量已知。
- 系统电压等级统一考虑为10kV馈线网络,或者110/10kV两级,看具体问题规模。
为了不让符号乱成一锅粥,建议统一的记号体系:
| 符号 | 含义 |
|---|---|
| 状态变量 (x_{ij}) | 线路(或主变)是否新建,0/1 |
| (P_i)、(Q_i) | 节点i的有功、无功注入 |
| (P_{ij})、(Q_{ij}) | 支路ij的有功、无功潮流 |
| (S_{ij}^{max}) | 支路ij的容量上限 |
| (V_i) | 节点i的电压幅值 |
| (V^{min})、(V^{max}) | 电压下限、上限 |
| (\Omega_L) | 所有支路集合,含现有和待建 |
| (\Omega_N) | 待建支路集合 |
| (c_{ij}) | 支路ij的新建投资费用 |
| (c_{sub}) | 新建变电站投资费用 |
目标函数写成:
[ \min \sum_{(i,j)\in\Omega_N} c_{ij} x_{ij} + \sum_{k\in\Omega_{sub}} c_{sub,k} y_k + \alpha \sum_{(i,j)\in\Omega_L} r_{ij} (P_{ij}^2+Q_{ij}^2) ]
最后一项是网损费用,(\alpha)是网损折算系数,把功率损耗折算成年度费用。如果你觉得非线性项让模型不好解,可以把它线性化——对于配电网规划问题,网损在目标函数里的权重通常远小于投资费用,直接使用线性化近似或忽略也在可接受范围内。实操中我更推荐先用“投资费用最小”为主目标,网损放在后校验阶段评估,这样模型的求解难度和可靠性都有保障。
3.2 N-1约束的数学表达
N-1约束是这个模型的核心,我尝试过两种写法,这里都交代清楚。
写法一:故障场景枚举法(通用性强,推荐)
把每条线路的断开当作一个独立场景,用(s)标记场景。正常态对应的变量带上标(0),故障态变量带上标(s)。于是每个场景(s)下都要满足功率平衡:
[ \sum_{j\in N(i)} P_{ij}^{(s)} = P_i^{(s)} \quad \forall i, \forall s ]
支路容量约束:
[ (P_{ij}^{(s)})^2 + (Q_{ij}^{(s)})^2 \leq (S_{ij}^{max})^2 \quad \forall (i,j), \forall s ]
这里有个关键的工程问题:故障态下,断开支路的潮流强制为0,即:
[ P_{ij}^{(s)} = 0, \quad Q_{ij}^{(s)} = 0 \quad \text{如果 } s \text{ 恰好断开 } (i,j) ]
这个约束很难直接写进MILP,因为“如果s断开(i,j)”本质上是一个索引逻辑。解决方法是:对于每个故障场景,直接删掉对应的支路方程,或者在建模时对每条支路单独设置一个故障版本。YALMIP的写法里,可以用循环逐个生成场景约束,简单粗暴且不容易错。
写法二:辐射网转供容量法(快速,但受限)
如果配电网严格辐射状,且联络开关都在指定位置,N-1校核可以近似为:某条馈线故障后,它下游的负荷能否通过联络线转移到相邻馈线,转供路径上的所有支路容量是否够用。
这个方法的好处是不用解潮流,只做容量累加和比较,计算速度快一个数量级。缺点是无法考虑电压约束和网损,而且对多联络、多转供路径的网络容易误判。我个人的结论是:方法二适合做大规模网络的初筛,方法一适合做精细化规划。如果你的题目明确要求“N-1扩展规划”,多半需要方法一的严谨性。
3.3 辐射状约束:这个坑务必提前规避
配电网与输电网最大的区别之一,就是配电网正常运行时必须保持辐射状(开环运行),否则继电保护配合会出问题。因此,在扩展规划模型里,必须加入“网络无环”的约束。
这个约束最常用的表达是“支路数 = 节点数 - 1”,加上网络必须连通的约束。但“连通性”在MILP里表达起来很麻烦。一个比较实用的技巧是:把问题建模成“单商品流”问题——选择一个根节点作为电源点,要求每个非电源节点都有且仅有一条从电源节点出发的路径到达它,这样网络自然就是树状结构。
具体实现上,可以增加辅助变量(f_{ij})表示从节点i流向节点j的“虚拟流量”,约束如下:
[ \sum_{j} f_{ij} - \sum_{k} f_{ki} = b_i \quad \forall i ]
其中(b_i)在电源节点取(N-1),在其他节点取(-1)。同时要求(f_{ij})只能存在于被选中的支路上:
[ f_{ij} \leq M \cdot z_{ij}, \quad z_{ij} \in {0,1} ]
这里(z_{ij})就是支路投建状态。(M)是一个足够大的常数(一般取节点数即可)。
这个写法在Matlab+YALMIP里实现并不难,但初学者特别容易忽略一个细节:(M)的取值如果过大,会导致求解器数值病态;如果过小,又会误限制可行域。我通常取(M = N_{node}),基本够用。
3.4 安全约束与目标函数之间怎么平衡
扩展规划模型中,“安全”和“经济”天然是一对矛盾。为了满足N-1,你可能需要多建一条线路,但这条线路在正常情况下负荷率很低,看着很不划算。面对这种矛盾,我的建议是:
- 把N-1作为硬约束,不允许“差不多”或“概率性通过”。因为规划水平的N-1不满足,意味着实际运行中的事故风险很高。
- 目标函数中的“网损费用”折减系数不要设得太大,否则优化算法会拼命通过新建线路来降损,算出来的方案投资大得惊人,实际工程无法接受。
- 如果实在解不动,可以做“N-1关键断面筛选”:先对候选线路集合做故障扫描,找出最容易越限的若干断面,只在模型中约束这些断面的N-1,其余断面后校验。这样代价是略保守,但模型规模大幅下降,求解速度显著提升。
4. Matlab实现过程:从数据表到求解器
4.1 整体程序架构
我习惯把Matlab程序拆成五大模块,每个模块一个脚本或函数,方便调试和复用:
data_input.m—— 录入网络拓扑、负荷、线路参数、待选方案。build_matrix.m—— 根据拓扑生成节点导纳矩阵、关联矩阵,同时生成决策变量的索引映射。formulate_model.m—— 用YALMIP定义变量、目标函数和全部约束。solve_model.m—— 调用求解器求解,输出运行状态和优化结果。post_process.m—— 输出规划方案表、N-1校核报告、绘制网架图。
模块化设计的好处是,当你换一个网络算例时,只需要改data_input.m里的数据表格,不必动数学模型代码。这一点在工程项目中非常加分——实际规划中你往往要在多个算例之间切来切去,一版代码搞定所有算例是最高效的模式。
4.2 数据准备:用一个简单算例说明
为了演示,我构造一个经典的三馈线算例:现有3座10kV母线(节点1、2、3),由上级变电站供电;负荷节点7个,候选新建线路5条。基础数据如下方表格所示(单位:MW、Mvar、欧姆):
| 节点 | 有功负荷 | 无功负荷 | 电压基准(kV) |
|---|---|---|---|
| 4 | 2.0 | 0.8 | 10 |
| 5 | 1.5 | 0.6 | 10 |
| 6 | 2.5 | 1.0 | 10 |
| 7 | 3.0 | 1.2 | 10 |
| 8 | 1.8 | 0.7 | 10 |
| 9 | 2.2 | 0.9 | 10 |
| 10 | 1.2 | 0.5 | 10 |
候选线路参数大致是:每条线电阻0.1欧姆/公里,电抗0.08欧姆/公里,长度2~4公里,容量5MVA,造价40万元/公里。这些参数虽然简单,但足以验证N-1约束是否生效。
在Matlab里,我习惯用表格对象存储线路参数:
lineTable = table(); lineTable.from = [1;1;2;2;3;4;5;6;7;8]; lineTable.to = [4;5;6;7;8;9;10;9;10;10]; lineTable.r = [0.2;0.3;0.25;0.4;0.3;0.2;0.25;0.3;0.2;0.15]; lineTable.x = [0.16;0.24;0.2;0.32;0.24;0.16;0.2;0.24;0.16;0.12]; lineTable.cap = ones(10,1)*5; lineTable.exist = [1;1;1;1;1;0;0;0;0;0]; % 1表示已有,0表示待建 lineTable.length = [3;2;4;2.5;3;2;3.5;2;4;3]; % 公里 lineTable.cost = 40 * lineTable.length; % 万元这里lineTable.exist字段非常关键,它区分了现有线路和候选线路,后续建变量时只用处理exist == 0的线路。这也是我踩过坑的地方——一开始我在所有线路上都加了0/1变量,导致求解结果把现有线路“切掉”了,网络拓扑完全错乱。从那以后我养成了习惯:凡是现有线路,固定其状态为1,不参与优化。
4.3 YALMIP建模代码:逐段讲解
使用YALMIP建模时,代码风格要尽量与数学模型一一对应,方便检查。核心步骤如下:
% 决策变量:候选线路投建状态(二进制) x = binvar(n_candidate, 1, 'full'); % 决策变量:正常态各支路潮流(连续) P = sdpvar(n_line, 1, 'full'); Q = sdpvar(n_line, 1, 'full'); % 决策变量:故障场景s下各支路潮流 % 用三维变量,第一维是支路索引,第二维是故障场景索引 P_s = sdpvar(n_line, n_fault, 'full'); Q_s = sdpvar(n_line, n_fault, 'full');这里我特意没有直接用Power = sdpvar(...)这样笼统的变量名,而是分开写(P)和(Q)。因为后面加约束时,有功和无功的功率平衡方程要分开写,变量名分开能有效减少混乱。
正常态的功率平衡约束,采用直流潮流近似的话非常简洁:
% 关联矩阵 A:行线对应支路,列对应节点 % A(i, from(i)) = 1, A(i, to(i)) = -1 Constraints = []; for i = 1:n_node idx_out = find(lineTable.from == i); idx_in = find(lineTable.to == i); Constraints = [Constraints, sum(P(idx_out)) - sum(P(idx_in)) == P_load(i)]; Constraints = [Constraints, sum(Q(idx_out)) - sum(Q(idx_in)) == Q_load(i)]; end注意这里P_load(i)和Q_load(i)是节点i的注入功率(负荷为正)。用直流潮流近似时,支路有功潮流和相角差成正比,但如果你不关心相角,只想校验容量,直流潮流的精确表达式可以不显式写出,直接用功率平衡就能得到一组可能的潮流分布——这在“可行性判定”场景里是可行的。
但是,要说清楚一点:纯直流潮流忽略了无功和电压,当你需要校核电压时就不够用了。我的做法是,正常态用交流潮流(调用Matpower的runpf)做一次精确校核,N-1场景先用直流模型筛选,筛选后再对通过方案做交流验证。这个“直流初筛+交流精验”的流程,既保证了N-1约束的可行性,又控制了计算量。
容量约束写成:
for i = 1:n_line Constraints = [Constraints, P(i)^2 + Q(i)^2 <= (lineTable.cap(i))^2]; end注意这是二次约束,等于把模型变成了MIQP(混合整数二次规划)。如果追求求解效率,可以改成线性化近似:用多边形内近似圆,比如用正八边形或正十六边形约束。这个方法非常实用:把圆形容量边界近似为正八边形后,模型变成MILP,Gurobi求解速度能快2到3倍,而精度损失在配电网规划这个尺度下完全可接受。
正八边形近似的Matlab代码如下:
% 用正八边形近似支路容量 c = cos(pi/8); s = sin(pi/8); for i = 1:n_line Constraints = [Constraints, ... c*P(i) + s*Q(i) <= lineTable.cap(i), ... c*P(i) - s*Q(i) <= lineTable.cap(i), ... -c*P(i) + s*Q(i) <= lineTable.cap(i), ... -c*P(i) - s*Q(i) <= lineTable.cap(i)]; end正八边形近似圆,内切面积约为实际圆的92%,外接面积约为108%,所以校核结果会略微保守,但工程上没有问题。
4.4 N-1故障场景的循环建模
这是整个程序最核心的循环。思路是:遍历每一条“关键线路”(建议先对非辐射联络线路做筛选,只保留馈线主干线路),生成一个故障场景,强制断开支路潮流为0,然后写入对应场景的功率平衡和容量约束。
n_fault = sum(lineTable.is_key); % 关键线路数量 fault_set = find(lineTable.is_key); % 关键线路索引 for k = 1:n_fault fault_line = fault_set(k); % 故障态功率平衡约束 for i = 1:n_node idx_out = find(lineTable.from == i); idx_in = find(lineTable.to == i); Constraints = [Constraints, ... sum(P_s(idx_out, k)) - sum(P_s(idx_in, k)) == P_load(i)]; Constraints = [Constraints, ... sum(Q_s(idx_out, k)) - sum(Q_s(idx_in, k)) == Q_load(i)]; end % 断开支路潮流强制为0 Constraints = [Constraints, P_s(fault_line, k) == 0, Q_s(fault_line, k) == 0]; % 故障态容量约束(同样用正八边形近似) for i = 1:n_line Constraints = [Constraints, ... c*P_s(i,k) + s*Q_s(i,k) <= lineTable.cap(i), ... c*P_s(i,k) - s*Q_s(i,k) <= lineTable.cap(i), ... -c*P_s(i,k) + s*Q_s(i,k) <= lineTable.cap(i), ... -c*P_s(i,k) - s*Q_s(i,k) <= lineTable.cap(i)]; end end这个循环看起来简单,但实际跑起来有几个坑:
第一,故障场景数量不要贪多。每条线路都当作故障场景的话,场景数量等于线路数,变量规模直接膨胀。我通过对实际工程的分析发现,配电网中真正影响N-1通过率的往往是馈线首端线路和联络通道,很多末端分支线路故障只影响局部少量负荷,对系统整体供电能力影响不大。因此,建议在建模前做一次“关键线路筛选”,把最需要校核的线路(通常是辐射主干、重载线路)选出来。
第二,场景约束与投建状态之间的耦合。待建线路在正常运行状态下的潮流没有被选中的话必须为0。也就是说,如果(x_{ij}=0),那么(P_{ij})和(Q_{ij})必须为0。这个约束要显式加上:
% 待建线路的状态约束 for i = 1:n_candidate line_idx = candidate_idx(i); Constraints = [Constraints, ... implies(x(i) == 0, P(line_idx) == 0), ... implies(x(i) == 0, Q(line_idx) == 0)]; endYALMIP里的implies约束很方便,但使用它有一个注意点:场景变量(P_s)、(Q_s)也要和投建状态联动。因为一条待建线路如果没有被选中,那么任何故障场景下它都不会存在,潮流也必须强制为0。我在早期版本漏掉了这个约束,结果求解器“聪明”地让一条没建出来的线路承担了N-1转供任务,方案输出后一检查就露馅了。
4.5 目标函数和求解器调用
目标函数比较简单,分为投资费用和网损(本例先忽略网损):
investment_cost = lineTable.cost(candidate_idx)' * x; if isempty(substation_idx) % 没有变电站候选时 total_cost = investment_cost; else total_cost = investment_cost + substation_cost' * y; end optimize(Constraints, total_cost, sdpsettings('solver', 'gurobi', 'verbose', 2));sdpsettings里我习惯开启Gurobi或Cplex,因为它们处理MILP的能力远超Matlab自带的intlinprog。如果没有商业求解器,intlinprog也能跑,但求解时间可能长很多,特别是场景数超过10、候选线路超过20条时,差距就非常明显。
4.6 辐射状约束在Matlab里的落地
前面提过单商品流方法,这里给出具体代码:
% 辅助变量:虚拟流 f_aux = sdpvar(n_line, 1, 'full'); % 根节点约束:假设节点1是根节点(电源) root_node = 1; for i = 1:n_node idx_out = find(lineTable.from == i); idx_in = find(lineTable.to == i); if i == root_node Constraints = [Constraints, sum(f_aux(idx_out)) - sum(f_aux(idx_in)) == (n_node - 1)]; else Constraints = [Constraints, sum(f_aux(idx_out)) - sum(f_aux(idx_in)) == -1]; end end % 虚拟流只能存在于可用线路上 for i = 1:n_line if lineTable.exist(i) == 1 Constraints = [Constraints, f_aux(i) <= M, f_aux(i) >= -M]; else line_candidate_pos = find(candidate_idx == i); Constraints = [Constraints, f_aux(i) <= M * x(line_candidate_pos), ... f_aux(i) >= -M * x(line_candidate_pos)]; end end这段代码的执行效果是:保证每个非电源节点都有且仅有一条从根节点流出的通路,从而保证网络是连通的树状结构。配合“支路数 = 节点数 - 1”(即所有现有线路+被选中的候选线路总数为(N-1)),网络就是严格的辐射状。这个约束我在多个算例里验证过,从未出现环网的错误结果。
4.7 求解后的后处理与可视化
求解完成后,要做的第一件事不是画图,而是“验证结果真的有N-1能力”。我会写一个独立的后校验脚本,对求解出的网架方案逐条断开线路,跑交流潮流,检查电压和载流量。这一步是必须的,因为优化模型用了直流近似或正八边形近似,可能存在微小偏差。
后校验的核心代码逻辑是:
% 根据优化结果构建最终网架 final_lines = find(lineTable.exist == 1 | x > 0.5); % 逐个N-1场景做交流潮流校验 for k = 1:length(final_lines) temp_lines = final_lines; temp_lines(k) = []; % 断开第k条线 % 构建节点导纳矩阵、调用Matpower或自己写牛顿拉夫逊潮流 % 检查电压和容量,记录越限信息 end后校验通过之后,再把规划前后的网架图画出来。Matlab的plot结合节点坐标,可以画出清晰的拓扑图,新增线路用红色虚线标出,现有线路用黑色实线。我在实际项目里,还会额外生成一张Excel表格,包含每条新建线路的起点、终点、长度、造价和投运建议,这张表可以直接交给设计院同事使用。
可视化部分一个简单示例:
figure; hold on; for i = 1:n_line x_coord = [node_coord(lineTable.from(i),1), node_coord(lineTable.to(i),1)]; y_coord = [node_coord(lineTable.from(i),2), node_coord(lineTable.to(i),2)]; if lineTable.exist(i) == 1 plot(x_coord, y_coord, 'k-', 'LineWidth', 1.5); else if value(x(find(candidate_idx==i))) > 0.5 plot(x_coord, y_coord, 'r--', 'LineWidth', 2); end end end plot(node_coord(:,1), node_coord(:,2), 'bo', 'MarkerSize', 8);这套后处理和可视化代码,我建议所有做规划的人都保留一份,因为不同项目之间大概率能复用,只是数据表换一换而已。
5. 常见问题与排查技巧实录
5.1 求解器报“无可行解”怎么办
这是N-1扩展规划建模里最让人头疼的问题。明明手算时感觉有解,求解器一跑却提示infeasible,而且不告诉你哪里冲突。我在实际调试中的经验是分四步排查:
- 检查待建线路是否真的能形成连通网络。有些算例给出的候选线路不足以把所有负荷连到电源点,那无论怎么优化都无解。此时观察
f_aux变量在松弛求解下的值,能定位到哪些节点不可达。 - 检查容量约束是否过紧。把故障态容量约束的上限放大10%,看是否有解;如果有,说明是线路容量不够,需要增加候选线路或提高容量。
- 检查N-1强制断开支路的索引是否正确。这是最容易出bug的地方——如果断开支路索引写错,相当于故障场景根本没断线,模型当然会认为“没问题”或“不收敛”。
- 检查
M取值。M太大会导致数值问题,太大会限制可行域。通常M = n_node即可,不需要取成(10^6)秒天级别的数。
5.2 求解时间过长如何加速
配电网扩展规划的求解时间和变量规模强相关。如果候选线路有50条,故障场景有20个,那变量规模会达到几千个,MILP求解可能要好几分钟甚至几小时。我常用的加速手段,按效果排序:
- 用正八边形近似代替二次容量约束,把MIQP改造成MILP,求解速度能快数倍。
- 只用关键断面做N-1约束,不做全场景枚举。
- 设置求解器的MIP Gap,比如
sdpsettings('gurobi.MIPGap', 0.01),允许1%的次优偏差,换取数量级的速度提升。 - 给变量提供热启动初值,尤其是用上一轮迭代的解作为下一轮初始解(在迭代规划或多场景规划时非常有效)。
5.3 为什么我的结果里出现“孤岛负荷点”
这是初学者最容易犯的错误——模型得到的网络中某些负荷节点是孤立的,压根没接到电源上。原因通常是:你只加了功率平衡约束,但没有显式约束“每个负荷节点必须被某条支路连接”。功率平衡约束只能保证这个节点的注入功率等于负荷,并没有强制它能够从电源获得功率。
解决办法就是前面说的单商品流辐射状约束,或者额外加一组“节点连通性”约束:每个负荷节点至少有一条被选中的支路与其相连。
for i = 2:n_node % 跳过源节点 conn_lines = find(lineTable.from == i | lineTable.to == i); conn_candidates = intersect(conn_lines, candidate_idx); existing_lines = intersect(conn_lines, find(lineTable.exist == 1)); if isempty(existing_lines) Constraints = [Constraints, sum(x(ismember(candidate_idx, conn_candidates))) >= 1]; end end这组约束虽然不能完整保证连通性,但能快速过滤掉明显孤岛的方案,辅助单商品流约束一起使用效果最好。
5.4 案例实测:求解结果与人工方案的对比
我拿上面的三馈线算例做了一次对比实验:
人工方案(凭经验:在负荷最重的节点7和节点8之间新建一条联络线,再在节点6和9之间建一条线)的总投资约为320万元,但N-1校验发现:断开节点1到节点4的馈线首端线路后,节点4的负荷无法通过任何联络转供,属于N-1不通过。
而扩展规划求解得到的最优方案是:新建节点4到9之间的联络线、节点6到9之间的联络线,此外还加强了一条10kV母线之间的联络通道。总投资约380万元,比人工方案贵了60万元,但所有关键N-1场景全部通过,且最大馈线负载率从95%降到了78%。
这个对比告诉我们一个很朴素的道理:人工方案往往“看着合理”,但N-1场景数量一多,大脑根本算不过计算机。扩展规划的价值不在于帮你“省钱”,而在于帮你“花对钱”——在同样的可靠性目标下,把资金投在最应该加强的断面上。
6. 工程应用中的几点补充经验
6.1 数据质量决定了模型质量
再漂亮的数学模型,喂进去的数据不对,输出就是一纸空文。做配电网规划时,最容易出问题的数据有三类:
第一,负荷预测数据。规划水平年的负荷是预测值,不是实测值。建议做多情景分析(高方案、中方案、低方案),至少跑三组,看规划方案对负荷不确定性的适应能力。
第二,待选线路的走廊资源。很多算法模型默认所有候选线路都能建设,但现实中可能因为道路、河流、已有建筑等因素导致有些走廊根本不可用。建议在建模前先做一次“可建性筛查”,只把实际可行的线路放入候选集合。
第三,线路容量参数的统一性。不同型号导线的载流量差异很大,建模时务必核对清楚,不要想当然地都用同一个容量值。
6.2 从“单目标”到“多目标规划”的扩展路径
本文介绍的是单目标(投资经济性)的N-1扩展规划。实际工程中,你往往还关心供电可靠性提升幅度、网损水平、变电站均衡度等多个指标。后续可以很自然地扩展成多目标优化:
- 把“N-1通过率”作为目标而不是硬约束,这样可以在“提高可靠性”和“降低投资”之间找Pareto前沿。
- 把“网损最小”加入目标函数,形成双目标规划。
- 加入“时序规划”维度,变成多阶段动态扩展规划,决策变量变成“某条线路在第几年投运”。
这个领域的研究空间很大,Matlab代码框架其实不需要推翻重来,只需要在目标函数和变量维度上做扩展,对已经跑通的建模代码来说改动成本很低。
对于正在做毕业设计的同学,我个人强烈建议在基础版N-1扩展规划之上,加入一个你感兴趣的扩展点,比如“考虑分布式电源接入后的N-1扩展规划”“考虑储能选址的N-1扩展规划”“考虑不确定性场景的鲁棒N-1扩展规划”,这样论文的创新点就非常扎实了。框架代码你已经有了,剩下的就是在这个骨架上填肉。
6.3 关于Matlab环境的一些实用建议
最后聊几个Matlab环境方面的实操建议,都是我在实际编码中踩过坑换来的经验。
- 尽量用较新的Matlab版本(R2021b以后),YALMIP对不同版本的兼容性整体很好,但旧的Matlab版本对部分求解器接口支持不全。
- YALMIP升级频繁,建议定期用
yalmiptest命令检查安装状态。遇到莫名其妙的问题,先升级YALMIP到最新版。 optimize求解完成后,用check(Constraints)检查约束违例情况。这个命令会逐条列出约束的最大残差,比单纯看求解器状态信息直接得多。- 如果遇到YALMIP与Gurobi接口不兼容,一般是路径顺序问题,确保Gurobi的matlab接口文件夹在YALMIP之前被加入路径。
- 复杂模型建议先把
verbose设为2,查看求解器输出的原始信息;模型跑通后再改回0或1,减少控制台刷屏。
个人经验里最实用的一条:不要等整个模型写完再调试。先把一个小算例跑通,再逐步放大到目标规模。我见过太多同学把整个模型写完之后,出了一堆bug,根本不知道从哪里开始查。从小算例起步,每加一个约束就运行一次,出问题了立刻定位,反而最快。
配电网N-1扩展规划是一个“看起来简单、做起来坑多”的方向。数学模型不复杂,但工程细节和代码实现上的坑比比皆是。希望这篇文章能帮你把该避的坑都提前避掉,真正把N-1扩展规划方法用到自己的项目或研究中去。如果在实操中遇到这篇文章没覆盖到的问题,欢迎按你自己的算法思路去推演——这种“自己推一遍、踩一遍坑”的过程,往往才是最扎实的学习方式。