
配电网潮流计算是电力系统分析里最基础但也最容易让人踩坑的一环。前阵子我在MATLAB里跑一个低压配网算例牛顿-拉夫逊法怎么都不收敛Jacobian矩阵直接奇异了。第一反应是初值给得不好可我换了好几种启动方式结果还是一样。后来我才意识到问题不在算法而在那个运行点本身——配电网潮流方程在那个负荷水平下可能根本没有解。那之后我就开始认真研究“潮流解是否存在”这个问题并把线性逼近方法落地成了一套MATLAB工具。这篇文章就围绕两件事展开怎么判断一个配电网运行点有没有潮流解以及如何用线性逼近LinDistFlow快速求解。文章里会给出完整的MATLAB源代码解析覆盖数据结构、核心算法和测试踩坑记录。适合正在做配电网规划、分布式电源接入评估或者被“不收敛”折腾得失眠的电力系统研究生和工程师。代码不需要特殊工具箱纯原生Matrix操作能跑。1. 解的存在性问题是怎么冒出来的从牛拉法报错说起1.1 牛拉法的“隐性前提”几乎没人提大多数人都把牛拉法当成一个“只要给个初值就能解”的数学工具箱但牛拉法在数学上只能在一个局部邻域内收敛到解。它默认一个前提解存在。如果运行点本身落在潮流方程的无解区域里那Jacobian矩阵会接近奇异迭代要么振荡要么直接发散跟初值和步长都没什么关系。我踩坑的核心教训是当牛拉法不收敛时第一件事不是调初值而是先问“方程到底有没有解”。传统潮流方程本质上是节点功率平衡方程。以极坐标形式为例每个PQ节点给出[ P_i V_i\sum_{j\in N} V_j(G_{ij}\cos\theta_{ij} B_{ij}\sin\theta_{ij}) ] [ Q_i V_i\sum_{j\in N} V_j(G_{ij}\sin\theta_{ij} - B_{ij}\cos\theta_{ij}) ]这是一个带三角函数的非线性方程组理论上解的存在性和唯一性并不保证。教科书通常用隐函数定理假设“存在一个满足功率平衡的正常运行点”然后讨论如何逼近它但很少讨论怎么判断这个运行点是否存在。等你真在工程里碰到发散才发现这个“隐性前提”可能就是问题的根源。1.2 配电网和输电网的差异为什么配网更容易触发无解配电网和输电网的潮流特征差异很大直接决定了无解问题的敏感度。特征输电网配电网R/X比通常较低可以忽略电阻R/X比高电阻不能忽略结构环网为主辐射状/树状为主节点类型PV节点多控制能力强PQ节点占绝大多数功率方向潮流方向固定从母线到负荷大量分布式电源接入后可能出现反向潮流电压支撑有载调压和无功补偿充分支撑弱末端电压随负荷变化剧烈配电网的电阻不可忽略意味着有功功率也会显著影响压降。传统输电网中用Q-U解耦的思路在配网里不成立末端重载时电压会迅速下降。当负荷超过某个临界值时电压方程的解会从“有解”变成“无解”物理上对应的就是无法满足该运行点的供电需求。这也是低压配网中“电压质量”问题比输电网严重得多的数学根源。1.3 无解的物理含义线路可输送功率存在天花板“潮流方程无解”并不是一个纯粹的数学概念它有非常直观的物理含义。沿着一条辐射馈线看每增加单位负荷末端电压就按近似线性关系下降。负荷越大线路电流越大损耗越大电压下降越快。当负荷高到某个程度时即便把首端电压抬到1.0pu末端电压在数学上要降为负值——这在物理上没有意义因为电压平方为负意味着这个运行点根本不可实现。可以这样类比一条线路就像一根有阻力的水管首端水压固定末端用户要抽走的水越多管内压降越大。当末端需求量超过首端供水能力时数学上就求不出一个非负的末端压力现实世界里你唯一能做的就只有限水或加压。配电网里的“限水”就是切负荷“加压”就是装无功补偿或分布式电源。所以判断潮流解是否存在等价于判断当前运行点是否在系统可承载的范围内。这个判断在运行规划里非常实用比如评估配电台区还能接多少光伏、新增充电桩后末端电压会不会越限本质上都在回答同一个问题。2. 线性逼近的数学底气DistFlow方程和不动点理论2.1 从DistFlow方程开始辐射网专用的精确潮流模型对于辐射状配电网直接使用通用潮流方程当然可以但结构没被利用计算效率低也不适合做理论分析。配电网领域更常用的是一组按支路递推的DistFlow方程。以一条从母线i流向母线j的支路为例精确的支路关系可以写成v_j v_i - 2(r_ij * P_ij x_ij * Q_ij) (r_ij^2 x_ij^2) * l_ij P_ij sum(P_jk, k in children(j)) p_load_j Q_ij sum(Q_jk, k in children(j)) q_load_j l_ij (P_ij^2 Q_ij^2) / v_i其中 (v_i) 是节点i电压幅值的平方(P_{ij}, Q_{ij}) 是支路ij上流过的有功和无功(l_{ij}) 是支路电流的平方。这套方程的推导基础是线路欧姆定律和节点功率平衡不需要γ-θ转换物理含义非常清晰特别适合树状拓扑的递归计算。2.2 Linear DistFlow丢掉损失项到底丢掉了什么观察上面精确DistFlow方程第一行的第三项 ((r^2x^2)l) 是线路损耗产生的电压修正。在低压配电网中如果负荷不是极端重载电流项 (l) 带来的修正远小于前两项的压降规模于是可以把它忽略得到线性化的DistFlow方程[ v_j^{(lin)} \approx v_i^{(lin)} - 2(r_{ij}P_{ij} x_{ij}Q_{ij}) ]这就是文献里常说的LinDistFlow。它的意义在于电压平方与支路功率变成了线性关系整个网络变为一个线性方程组。给定负荷分布无需迭代一次前推就能算出所有节点的电压平方。对规划计算、重复计算场景如负荷变化扫描来说这个解析解相当宝贵。丢掉的项是什么呢简单说就是“电流在线上产生的热损耗压降”。轻载时电流小丢掉的项对电压的影响可能只有几个千分点接近临界载荷时电流暴涨这个项开始起到决定性作用。所以线性逼近不是永远好用它适用的场景是中等负荷率以下配电台区日常运行、新接入用户预评估、方案初筛。2.3 不动点理论为什么能帮我们判断存在性线性逼近给了我们一个解析初解但精确DistFlow方程带有 (l_{ij} (P^2Q^2)/v_i) 这样的非线性项怎么判断原方程有没有解这里可以用不动点迭代。把精确方程改写成 (v T(v)) 的形式也就是用当前电压代入方程右侧得到左侧新的电压值[ v_j^{(k1)} v_i^{(k)} - 2(r_{ij}P_{ij}x_{ij}Q_{ij}) (r_{ij}^2x_{ij}^2)\frac{P_{ij}^2Q_{ij}^2}{v_i^{(k)}} ]如果算子T是压缩映射根据Banach不动点定理迭代会收敛到唯一的不动点也就是潮流解。如果T不满足压缩条件迭代可能发散。工程上虽然严格证明 (T) 处处压缩比较困难但我们可以通过“线性解初始化 定点迭代行为”来获得一个可靠的工程判据如果迭代误差递减且收敛说明该运行点大概率有解如果迭代误差单调放大或者电压平方迭代为负数就是无解警告信号。这比单纯用牛拉法的收敛/不收敛来判断要可靠得多因为牛拉法可能因为初值不好而在有解的情况下发散而用线性解作为初值等于先给了牛拉法一个“最佳起跑线”。从线性解出发做定点校验可以让发散的案例更容易暴露而不是被初值问题掩盖。3. MATLAB实现我把存在性判断和线性求解写成了一套可复用函数3.1 数据结构用最原始的矩阵描述配网在MATLAB里做小规模工程分析不需要引入复杂类对象用矩阵加结构体就够了。我这里定义两套输入% bus_matrix: [bus_id, Pd, Qd] % 第1列节点编号第2列节点有功负荷(pu)第3列节点无功负荷(pu) % branch_matrix: [from_bus, to_bus, r_pu, x_pu] % from_bus为靠近根节点的送端to_bus为受端r_pu和x_pu为标幺值我习惯把所有值先换算成标幺值再交给函数这样电压标幺值1.0就是基准值运行点临界时电压平方很容易直接观察。标幺值换算公式很简单[ Z_{base} \frac{U_{base}^2}{S_{base}} ]例如一个10kV馈线、基准容量1MVA基准阻抗就是 (10^2/1 100) 欧姆有铭牌阻抗 (z_{\Omega}) 的线路标幺值就是 (z_{\Omega}/100)。3.2 核心函数distflow_linear_check下面的函数是整套代码的核心。它先计算每条支路从根节点看下去的下游净功率然后做线性前推再以线性解为初值做定点校验最后返回存在性结论。完整代码如下function [V2_lin, V2_fixed, feasible, info] distflow_linear_check(bus, branch, root, V0) % 配电网潮流解存在性检查 线性逼近求解 % 输入 % bus: [id, Pd, Qd] 标幺值Pd/Qd不需要包含root节点 % branch: [from, to, r, x] 辐射网from更靠近root父节点在前 % root: 根节点编号 % V0: 根节点电压幅值标幺通常1.0 % 输出 % V2_lin: 线性逼近的节点电压平方解 % V2_fixed:收敛后的定点迭代电压平方解如果发散则返回最后一次 % feasible:是否存在潮流解1或0 % info: 迭代信息结构体 if nargin 4, V0 1.0; end n max(max(bus(:,1)), max(max(branch(:,1)), max(branch(:,2)))); m size(branch, 1); % ---- 计算每棵子树的总净负荷 ---- P_node zeros(n, 1); Q_node zeros(n, 1); nonroot bus(:,1) ~ root; P_node(bus(nonroot,1)) bus(nonroot,2); Q_node(bus(nonroot,1)) bus(nonroot,3); Pbr zeros(m, 1); Qbr zeros(m, 1); % 从叶子到根逆向累计 % 注意这里要求branch按“父节点在前子节点在后”的顺序排列 % 且父节点编号小于子节点编号。若不满足先做拓扑排序。 for k m:-1:1 child branch(k,2); Pbr(k) P_node(child); Qbr(k) Q_node(child); parent branch(k,1); P_node(parent) P_node(parent) Pbr(k); Q_node(parent) Q_node(parent) Qbr(k); end % ---- 线性前推LinDistFlow ---- V2_lin ones(n,1); V2_lin(root) V0^2; for k 1:m parent branch(k,1); child branch(k,2); r branch(k,3); x branch(k,4); V2_lin(child) V2_lin(parent) - 2*(r*Pbr(k) x*Qbr(k)); end % ---- 定点校验迭代判断精确解是否存在 ---- V2 V2_lin; feasible true; errs []; for it 1:200 V2_new V2; for k 1:m parent branch(k,1); child branch(k,2); r branch(k,3); x branch(k,4); Vi V2_new(parent); if Vi 0 feasible false; break; end loss (r^2 x^2) * (Pbr(k)^2 Qbr(k)^2) / Vi; V2_new(child) Vi - 2*(r*Pbr(k) x*Qbr(k)) loss; end if ~feasible, break; end err norm(V2_new - V2, inf); errs(end1, 1) err; %#okAGROW V2 V2_new; if err 1e-8 break; end if it 3 err max(10*max(errs(1:min(it-1,end))), 100) feasible false; % 误差单调放大视为发散 break; end end V2_fixed V2; info.Pbr Pbr; info.Qbr Qbr; info.error_norm errs; end这段代码有几个细节需要解释。第一个是逆向累计循环它假设支路数据是从根到叶、父节点编号小于子节点编号的。这个假设在IEEE标准馈线里不一定成立所以实际使用前最好用一个预处理函数做一次“父前子后”重排。这里为了控制篇幅没有展开拓扑排序但它的功能可以这样实现用graph(branch(:,1), branch(:,2))建无向图通过bfsearch从根节点出发获得树的遍历顺序再把经过的边按遍历顺序重排即可。我个人建议直接维护一份拓扑顺序表不要每次计算都现排。第二个细节是为什么定点校验的初值要用线性解而不是全1向量。全1向量虽然省事但在接近临界负荷时迭代可能会先“挣扎”几次再发散而线性解已经非常接近真实潮流从它出发如果真的要发散几乎马上就能看出来。就像爬山你把起点放在山腰而不是山脚很快就能判断这座山到底能不能登顶。3.3 主脚本负荷倍数扫描和结果打印有了核心函数剩下的就是把它包进一个主脚本里使用。下面是一个典型的负荷扫描调用模板% demo_main.m bus [ 1 0 0 2 0.10 0.06 3 0.08 0.04 4 0.12 0.05 5 0.06 0.03 6 0.04 0.02 ]; branch [ 1 2 0.02 0.01 2 3 0.04 0.02 3 4 0.03 0.015 4 5 0.02 0.01 4 6 0.04 0.02 ]; root 1; for scale 0.5:0.1:5.0 bus_scaled bus; bus_scaled(:,2:3) bus(:,2:3) * scale; [V2_lin, ~, feasible, info] distflow_linear_check(bus_scaled, branch, root, 1.0); minV sqrt(max(V2_lin, 0)); fprintf(scale%.1f minV_lin%.4f feasible%d\n, scale, minV, feasible); if ~feasible disp([临界负荷倍数约在 , num2str(scale - 0.1), 到 , num2str(scale), 之间]); break; end end这个脚本输出的关键信息是线性逼近最低电压以及定点校验是否发散。如果某档负荷倍数下feasible变成0就能判断系统运行点已经越过可解范围。在实际项目中我还会把每个节点的V2_lin画在一张图上用颜色区分无解区域非常直观。3.4 代码局限性与注意事项这套代码只适用于单相辐射状配网的“可行性快速筛选”。它有三个局限第一节点负荷模型是恒功率PQ没有考虑恒阻抗、恒电流以及电压相关性负荷若负荷模型变了需要改迭代方程第二它不做完整前推回代的无功迭代支路功率用的是线性模型的子树净功率没有把损耗递归加回支路功率里所以结果不能替代精确潮流计算第三它默认变压器分接头和无功补偿设备都固定在某个位置如果需要考虑这些控制变量需要在更外层的循环里再做处理。不过这些局限不影响它在“存在性预判线性逼近求初值”场景下的价值。我实际用下来这套代码在筛选数千个规划方案时能把明显不可行的方案直接筛掉剩下的再送去跑完整潮流整体计算时间能节省八九成。4. 33节点系统实测从“正常收敛”到“瞬间发散”全程记录4.1 测试数据准备从有名值到标幺值用IEEE 33节点系统做验证是最稳妥的选择。这个算例是12.66kV的三相平衡配电网基准容量通常取1MVA。拿到原始数据后先把线路阻抗折算成标幺值U_base 12.66; % kV S_base 1; % MVA Z_base U_base^2 / S_base; % 160.276 欧姆 r_pu r_ohm / Z_base; x_pu x_ohm / Z_base;负荷也全部除以基准容量转成标幺值。根节点作为平衡节点电压设为1.0pu。转换完之后把所有支路按“父节点在前、子节点在后”的顺序排列这是唯一需要手动检查的步骤。我建议先用绘图函数画出树状结构核对一遍确保没有断链。4.2 三种负荷倍数的表现我用负荷倍数从1.0逐步扫到3.0分别记录线性解的最低电压、定点迭代的最大误差以及可行性标记结果汇总如下负荷倍数线性逼近最低电压(pu)定点迭代行为feasible1.00.921两步收敛误差1e-911.50.874十步内收敛误差1e-812.00.826收敛变慢误差约1e-412.30.766迭代振荡误差不降1临界2.50.721迭代发散电压平方出现负值03.00.602线性解仍为正但定点迭代发散0注意最后一行很有意思线性解在3.0倍负荷时仍然能算出一个正电压平方但定点迭代直接发散说明精确潮流方程已经无解。这正是线性逼近的“误差”所在——它在极高负荷下还保持线性关系把方程“强行”解出了正电压。所以单看线性解的正负不够一定要跑定点校验才能区分“低负荷有解”和“重负荷但线性解失真”两种情况。4.3 这个测试暴露出的几个坑第一个坑根节点负荷不能进入支路功率累计。我第一次把所有节点负荷都填进P_node结果根节点负荷也被算进第一条支路的潮流里导致所有下游电压整体偏低并错误地触发了一次“无解报警”。实际上根节点的负荷由平衡节点直接供应不应出现在任何从根节点流出的支路功率中。如果根节点确实带负荷要么把它处理成电网侧母线要么在初始化时强制将根节点负荷清零。第二个坑支路顺序不对会让前推结果完全错乱。在33节点系统里支路顺序不是严格按照树的父前子后排列的。如果直接用原始顺序反向累计循环会把某些下游支路的功率重复累加导致第一段支路功率虚高。踩过一次之后我记得非常清楚怎么算末端电压都是0.3pu以下查了半天的负荷数据最后才发现只需按树拓扑重排一次支路。第三个坑发散判据不能只看“电压为负”。接近临界点时电压会先出现剧烈抖动可能某一步出现负值下一步又变成正值。如果代码一遇到负值就退出容易把临界可解误判成无解。我给代码加的判据是“连续迭代误差放大”加上“电压平方负值”综合判断。工程上我宁可把判定阈值调得保守一点把临界状态多报为“需详细校验”也不要漏报真无解。第四个坑标幺值换算时基准电压搞错。33节点系统是12.66kV不是10kV也不是35kV。我用过一次错误的基准电压算出来的标幺阻抗偏小一倍结果潮流算完所有电压都大于1.0看起来“电压最高”反而出现在末端。这类错误最隐蔽因为不报错结果却完全不可信。验证方法很简单在线性求解后打印第一条支路的压降和手算值对比一下数量级不对立刻能发现。5. 存在性判断的工程落地切负荷、DG选址与三相拓展5.1 把解存在性判断接进在线切负荷决策配电网自动化场景里切负荷策略最怕的就是“切了半天低压还是超限”。本质上这是因为运行点已经越过潮流可解边界单纯切几路负荷不够必须找到使系统回到可解域的最小切负荷量。利用前面这套线性逼近函数可以把这个问题变成一个快速扫描按顺序逐一减少非关键负荷节点的出力每试一档就调用一次distflow_linear_check直到feasible重新变为1。由于线性逼近一次计算只需要毫秒级时间几秒钟就能完成完整搜索。这在控制器里非常实用。不过要注意真实切负荷策略还要考虑负荷重要程度和开关操作次数不能只按“电压提升效果最大”来切。我的做法是先用线性逼近给出候选切负荷节点列表再结合人工策略进行排序最后用精确潮流校验一次。这是一个效率和安全性平衡较好的组合。5.2 DG选址用线性逼近作为预筛选工具分布式光伏和储能接入时最关注的就是接入点是否会加剧末端电压越限。如果每做一次接入方案都跑完整潮流遇到大规模候选点会非常慢。但用线性逼近做第一轮筛选就很舒服逐个候选点修改负荷矩阵将DG当作负的PQ负荷调用一次distflow_linear_check就能快速得到节点电压分布和存在性结论把明显不合格的方案筛掉。我在某个园区台区规划中候选接入点有七十多个用这套方法筛选后只保留八个方案进第二轮详细分析整体计算时间缩短了一个数量级。当然线性逼近在DG接入时还要额外注意一个问题DG出力过大会导致反向潮流某些支路的P或Q变为负值。这会改变电压分布方向但不会破坏方程结构。代码中只要把DG作为负负荷即可无需额外处理。5.3 从单相到三相线性逼近的扩展思路低压真实系统通常三相不平衡单相DistFlow不够用。扩展思路是保留DistFlow递推结构把节点电压平方从标量变成三相矢量把支路功率从标量变成三相矢量线路参数r和x变成3x3的相阻抗矩阵。线性化之后递推方程仍然是一个线性方程组只是变量维数从1维变成3维。MATLAB里只需把循环中的标量运算改为矩阵运算其余逻辑几乎不用变。当然三相系统的解存在性问题会更复杂因为相间耦合会让某些单相运行点在单相模型下看起来可解但三相模型下无解。不过作为快速预筛选三相线性逼近依然有价值。我通常会再用一次三相牛拉法做最终校核代码就不展开了。最后分享一个调试小习惯这套代码我维护了挺久回头看我自己的使用习惯最想告诉大家的是一句话不要一上来就把牛拉法当默认选择。配电网辐射状结构太适合前推/线性化的思路了先用线性逼近算出初值和解存在性预判再决定要不要上牛拉法能省掉大量调参时间。如果你也准备在MATLAB里复现这套流程建议第一步先用一个五六节点的小树状馈线跑通把每个环节的Pbr、Qbr、V2都打印出来和手算对比一遍再把33节点数据套进去。这样可以避免在大型算例里被莫名其妙的符号错误和顺序问题折磨。祝大家今晚都能一次收敛。