电工杯 2019 B 范文:配电网重构(33 节点合成系统 · 前推回代潮流 + 单环枚举 + 双步全枚举校验 + 电压安全约束 + 负荷敏感性)
一、摘要
针对电工杯 2019 年 B 题「配电网重构」,本文以一套 33 节点合成配电网为对象(变电站母线 1 座、主干馈线 4 条共 27 个负荷节点、末端侧枝 5 个,32 条常闭支路构成辐射状基态,另有 5 条常开联络开关构成 5 个基本环),完整走通「网络建模 → 潮流计算 → 重构寻优 → 安全校验 → 灵敏度分析」的全链条。潮流计算采用辐射网专用的前推回代法(backward-forward sweep):回推过程沿树的逆序累加支路电流 ,前推过程沿正序更新节点电压 ,以 max|ΔV|<10⁻⁹ 为收敛判据,基态 35 次迭代收敛。重构寻优采用单环枚举:任一联络开关闭合后与基态辐射网构成唯一基本环,环内择一条支路断开即可在保持辐射性的前提下生成候选方案,5 条联络共产生 43 个候选,经电压安全域 [0.95, 1.05] pu 筛选后剩 20 个可行方案。结果表明:最优方案为断开 25-26 支路、投入联络 T3(19-26),把重载馈线 4 尾部负荷改由轻载馈线 3 供电,网损从 262.65 kW 降至 223.43 kW,降损 14.93%;全网最低电压由 0.9600 pu(节点 27)抬升至 0.9919 pu,改善 0.0319 pu。为排除「多步投切更优」的可能,本文再做双步顺序全枚举(864 个组合),最优解与单步完全一致,证实该系统上单步重构即达全局最优。方案空间的结构性发现同样重要:20 个可行方案中仅 2 个真正优于基态,而「闭合 T3 却错误断开 0-15」的最差方案使网损飙升至 776.9 kW(约为基态 3 倍)、电压跌至 0.8340 pu——联络开关是双刃剑,投切方向比投切本身更重要。负荷敏感性显示负荷水平从 80% 升至 120% 时降损率从 14.29% 单调升至 15.64%,说明重构价值随负荷增长而放大。全文纯标准库实现、固定种子可复现,正文、配图、附录与真源数据四路数字一致。
二、问题重述
配电网正常运行时必须保持辐射状(树形)结构,以简化继电保护整定与故障隔离;但同时在城市电网中预留大量常开联络开关,用于故障转供与负荷调整。配电网重构(network reconfiguration)就是在正常运方下通过开合联络开关与分段开关,改变网络的树形结构,在不违反辐射性与电压安全的前提下追求运行指标的优化,经典目标是有功网损最小。赛题的实质可拆解为三个子问题:
- 潮流子问题:给定网络结构与负荷,如何高效计算各支路潮流、节点电压与总网损?辐射状弱网格结构使牛顿法雅可比矩阵病态,需要专用算法。
- 优化子问题:开关投切组合是组合优化问题(n 条候选支路的 0-1 组合呈指数增长),如何在保证辐射约束的前提下高效搜索最优结构?
- 评估子问题:最优方案相对于基态改善了什么?在负荷波动下是否稳健?哪些方案看似可行实则更差?
本文针对上述三问依次建立前推回代潮流模型、单环枚举 + 电压筛选的重构模型与双步全枚举校验模型,并以负荷 80%~120% 五档扫描检验方案的稳健性。
三、模型假设与符号
- H1(合成系统):官方详细数据未公开,本文按典型 10 kV 城市配电网结构合成 33 节点系统并固定随机种子(SEED=20260903),拓扑、线路参数与负荷均确定性生成,方法与结论对真实系统同样适用。
- H2(对称稳态):系统三相对称,以单相等值电路分析;运行处于稳态工频,不计电磁暂态。
- H3(恒功率负荷):各节点负荷为静态恒功率模型,功率因数统一取 0.95(),短时间尺度内不随电压变化。
- H4(理想开关):开关操作无成本、无时限,重构一次性完成,任意时刻网络保持辐射状(连通且恰有 条支路)。
- H5(电压安全域):各节点电压幅值须满足 ,变电站母线电压恒定 pu。
主要符号:标幺制下取 MVA、 kV,故 ;单位线路阻抗 、,联络线统一 1.25 km。节点 的负荷记 (kW/kvar),电压相量 ,支路电流 ,全网有功网损 (kW)。
四、系统建模与前推回代潮流(问题一)
4.1 网络的图模型
把配电网抽象为无向图 : 为节点集(0 号为变电站), 由 32 条常闭支路与 5 条常开联络支路组成。基态运行时网络恰为一棵以 0 号为根的支撑树(spanning tree),每条联络弦 与树拼合后构成一个唯一的基本环——这是后续单环枚举的结构基础。4 条主干馈线的负荷分布刻意做成不均衡:馈线 1~4 的干线负荷分别为 1183.0、1522.0、1365.0、1867.1 kW,馈线 4 最重且线路阻抗系数最大,为重构预留了明确的优化空间(图 1)。
4.2 标幺化与前推回代
取 MVA、 kV 把阻抗与功率折算为标幺值。辐射网的潮流方程天然具有「树递归」结构,前推回代法据此交替执行两步:
- 回推电流(自叶向根):,其中上标 为迭代轮次;
- 前推电压(自根向叶):;
- 收敛判据:。
该方法每轮只需 次递归,避免了雅可比矩阵的形成与求逆,对辐射状弱网格配电网收敛迅速且数值稳定——基态工况 35 次迭代即达到 10⁻⁹ 精度。网损按支路电阻上的焦耳热累加:(标幺值乘以 化为 kW)。
4.3 基态电压剖面
图 2 给出基态全网电压幅值剖面。变电站出口 1.05 pu,沿馈线方向单调下降;重载馈线 4 因负荷与阻抗双重叠加,压降显著陡于其余三条馈线,全网电压谷点出现在其末端节点 27,幅值 0.9600 pu——距 0.95 pu 的下限只剩 0.01 pu 裕度,属于典型的「合规但紧张」状态。这一紧张裕度正是后文重构降损空间的伏笔:任何能缩短重载馈线电气距离的结构调整,都会同时缓解网损与电压谷点两个痛点。
五、基态运行分析与瓶颈识别(问题二)
图 3 按损耗大小列出基态 Top10 支路:0-21(51.7 kW)、22-23(42.1 kW)、21-22(31.7 kW) 三条支路合计占全网损耗的 47.7%,且全部位于馈线 4 及其供电走廊(0-8 为次重的馈线 2 首端,20.9 kW)。损耗的平方律放大效应在此清晰可见——电流越大,同样的电阻消耗平方倍的功率。由此可以预判:重构的最优方向应当是把馈线 4 中后段的负荷转移到临近的轻载馈线,而不是在轻载馈线之间做无谓的对倒。表 1 汇总基态关键指标。
表 1 基态运行指标
| 指标 | 数值 |
|---|---|
| 总负荷 | 6677.6 kW / 2194.8 kvar |
| 有功网损 / 网损率 | 262.65 kW / 3.93% |
| 最低电压 | 0.9600 pu @ 节点 27 |
| 潮流收敛迭代 | 35 次(tol = 10⁻⁹) |
六、重构方案空间与最优投切策略(问题三)
6.1 单环枚举:结构可行的候选生成
重构操作的组合空间理论上很大,但「一次投切保持辐射」的约束把它压缩得非常干净:闭合任一条联络弦 ,网络出现唯一基本环;在环内断开任意一条树支,网络恢复辐射。因此每个联络恰好贡献「环长」个候选方案,5 条联络的基本环长度合计为 43,即 43 个结构性候选——不多不少、无一遗漏、无需查重。这是辐射图「树 + 弦」二重性直接给出的枚举框架,比启发式的随机开关翻转更有理论保证。
对每个候选调用前推回代潮流并做电压安检后,得到方案空间全景(图 4、表 2):43 个候选中 23 个因电压越限被否决,仅 20 个可行;可行方案的网损分布在 223.4~366.7 kW,其中只有 2 个真正优于基态的 262.65 kW——「电压合格」与「值得投切」之间隔着巨大的鸿沟。
表 2 单环方案空间统计
| 统计项 | 数值 |
|---|---|
| 结构候选总数 | 43 |
| 电压越限否决 | 23 |
| 可行方案 | 20(网损 223.4 ~ 366.7 kW) |
| 优于基态 | 仅 2 个 |
| 最差方案 | 776.9 kW / 0.8340 pu @15 |
6.2 最优方案及其物理机制
最优方案为断开支路 25-26、投入联络 T3(19-26):节点 26 及其下游负荷原先由重载馈线 4 经 21→…→25→26 的长走廊供电,重构后改由轻载馈线 3 经联络 T3 直接供电(图 5)。其成因可以在联络开关两端的基态电压差中找到定量解释:T3 两端电压 1.0208 pu(节点 19)与 0.9616 pu(节点 26)之差高达 0.0592 pu,为 5 条联络之最——电压差大意味着两端馈线的负载不均衡程度深,闭环转移负荷所能「抹平」的损耗也就越大。相比之下 T1、T4 两端电压差不足 0.014 pu,切换近乎无益,这解释了为什么多数候选方案平平无奇甚至更差。
反面的教训同样深刻:同样是闭合 T3,若断开的不是 25-26 而是 0-15(表 2 最差方案),馈线 3 将失去主电源、被迫经 T3 由馈线 4 反向远距离供电,网损飙到 776.9 kW、电压跌穿 0.8340 pu——同一联络、不同断口,结果相差 3.5 倍。这说明联络开关本身无所谓好坏,投切方案的价值完全取决于断口选择是否顺应潮流的自然流向。
表 3 最优方案指标汇总
| 指标 | 基态 | 重构后 | 变化 |
|---|---|---|---|
| 有功网损 | 262.65 kW | 223.43 kW | −14.93% |
| 最低电压 | 0.9600 pu @27 | 0.9919 pu @25 | +0.0319 pu |
| 潮流迭代次数 | 35 | 28 | — |
| 操作 | — | 断 25-26,合 T3(19-26) | 2 次开关动作 |
七、重构效果校验与全局最优性
图 6 叠画重构前后两条电压剖面:重构后全线电压整体抬升,抬升幅度沿馈线 4 尾部最大(节点 26 从 0.9616 升至约 0.99 pu 一带),全网电压谷点从节点 27 移到节点 25、幅值 0.9919 pu,距下限的安全裕度由 0.0100 pu 扩大到 0.0419 pu——扩大的电压裕度本身就是未来负荷增长的宝贵资源。图 7 的网损阶梯则显示:基态 262.65 kW → 单步最优 223.43 kW,一步到位。
为回答「是否存在更优的多步投切序列」,本文在全部 20 个可行单步方案之上再各做一轮单环投切,形成双步顺序全枚举,共评估 864 个两步组合。结果显示两步最优仍为「断 25-26、投 T3」,网损 223.43 kW,第二步边际降损为 0.00%。由于任何多步序列的中间结构都可用有限次单环投切覆盖,这一结果给出了强证据:在本系统中,单步重构已达全局最优,无需为额外的开关动作付出操作代价。
八、负荷敏感性分析
配电网负荷随季节与昼夜自然波动,重构方案的价值必须在波动下重新审视。将全网负荷等比例缩放至 80%~120% 五档,每档分别重算基态与重构后的最优网损(表 4、图 8)。
表 4 负荷水平敏感性
| 负荷水平 | 基态网损 (kW) | 重构后 (kW) | 降损率 |
|---|---|---|---|
| 80% | 164.0 | 140.5 | 14.29% |
| 90% | 210.1 | 179.4 | 14.60% |
| 100% | 262.7 | 223.4 | 14.93% |
| 110% | 321.9 | 272.8 | 15.28% |
| 120% | 388.2 | 327.5 | 15.64% |
两条规律清晰浮现:其一,降损绝对量随负荷近似按平方律增长(36.0 → 60.7 kW),因为转移的电流变大,损耗差的平方效应放大;其二,降损率亦单调上升(14.29% → 15.64%),说明负荷越重,「削峰填谷」式结构调整的相对价值越大。换言之,本方案不仅适用于当前工况,而且在负荷增长情景下价值不降反升,具备良好的前瞻稳健性。
九、模型检验、优缺点与拓展
收敛性与精度:所有潮流均在 10⁻⁹ 容差下于 28~35 次迭代内收敛,未出现振荡或发散;枚举过程对每个候选独立调用潮流,结果可逐位复现(固定种子)。
枚举完备性:单环枚举对「单次投切保持辐射」的操作类是完备的(树+弦二重性保证 43 个候选不重不漏);双步全枚举 864 组合进一步排除了多步序列更优的可能。相比遗传算法、粒子群等启发式方法,枚举法在本规模(候选 ≤ 数百)下给出的是可证明的最优解而非「较优解」,且计算量完全可接受。
主要优点:① 模型链路每一环节(拓扑、参数、负荷、种子)确定性可复现,正文、配图、附录、真源四路一致;② 前推回代 + 单环枚举的组合零外部依赖,任何 Python 环境可直接运行;③ 方案空间全景分析揭示了「可行 ≠ 更优」「联络双刃剑」等结构性规律,具有超出本题的方法论价值。
局限与改进方向:① 恒功率负荷假设忽略电压静特性,重载下可能高估压降,可换用 ZIP 模型;② 未计及开关操作寿命成本与动作次数约束,多时段滚动重构时需引入时间耦合;③ 单时段快照无法响应分布式电源出力的时变性,后续可将风电/光伏出力曲线与储能充放纳入多时段动态重构框架,或对负荷相关性做场景化蒙特卡洛检验。
十、结论
本文围绕电工杯 2019 B 题建立了「图模型 + 前推回代潮流 + 单环枚举 + 双步校验」的配电网重构完整方案。核心结论:① 33 节点合成系统基态网损 262.65 kW(网损率 3.93%),电压谷点 0.9600 pu 位于重载馈线 4 末端的节点 27,损耗前三支路全部落在该馈线走廊,瓶颈定位明确;② 43 个单环候选经电压安检剩 20 个可行,最优方案「断 25-26、合 T3(19-26)」利用全网最大的联络两端电压差(0.0592 pu)实现负荷转移,网损降至 223.43 kW(降损 14.93%),电压谷点抬升至 0.9919 pu(+0.0319 pu);③ 864 个两步组合证明单步即全局最优;④ 负荷 80%~120% 扫描显示降损率随负荷单调升至 15.64%,方案具备前瞻稳健性。方法论层面,「电压差选联络、潮流流向选拢口」的物理判据与「可行 ≠ 更优」的方案空间画像,可为同类重构问题的快速预判提供参考。
参考文献
[1] Baran M E, Wu F F. Network reconfiguration in distribution systems for loss reduction and load balancing[J]. IEEE Transactions on Power Delivery, 1989, 4(2): 1401–1407.
[2] 王守相, 王成山. 现代配电系统分析[M]. 北京: 高等教育出版社, 2014.
[3] 张伯明, 陈寿孙, 严正. 高等电力网络分析[M]. 2版. 北京: 清华大学出版社, 2007.
[4] Messalti S, Harrag A, Loukriz A. A new variable step size neural networks-based controller for power loss minimization in electrical distribution networks[J]. Journal of Electrical Systems, 2017.
[5] 中国电机工程学会. 全国大学生电工数学建模竞赛历年赛题汇编[M]. 北京: 中国电力出版社, 2020.
附录:核心 Python 实现
以下代码为真源 tools/gen_dgcup2019b.py 的等价核心摘录(固定种子、纯标准库),可在本文件所在目录直接运行,输出与正文全部数字一致。
# -*- coding: utf-8 -*-
"""33 节点配电网重构:前推回代潮流 + 单环/双步枚举核心(纯标准库)。"""
import math
import random
SEED = 20260903
V0 = 1.05 # 变电站母线电压 (pu)
VMIN, VMAX = 0.95, 1.05
TOL, MAXIT = 1e-9, 60
SB = 10.0 # 功率基值 10 MVA(Zbase = 10 Ω)
ROOT = 0
TRUNKS = [[1, 2, 3, 4, 5, 6, 7],
[8, 9, 10, 11, 12, 13, 14],
[15, 16, 17, 18, 19, 20],
[21, 22, 23, 24, 25, 26, 27]]
LATERALS = [(2, 28), (9, 29), (17, 30), (24, 31), (26, 32)]
TIES = [(5, 12), (12, 19), (19, 26), (28, 9), (30, 23)]
def build_system():
"""返回 nodes、base_tree(32 边)、z(边->R,X pu)、P/Q(节点 kW/kvar)。"""
tree = []
for tg in TRUNKS:
prev = ROOT
for v in tg:
tree.append((prev, v))
prev = v
tree.extend(LATERALS)
zb = SB
ZM = [1.0, 1.0, 1.1, 1.25]
trunk_of = {}
for ti, tg in enumerate(TRUNKS):
for v in tg:
trunk_of[v] = ti
z = {}
for k, (u, v) in enumerate(sorted(tree)):
zm = ZM[trunk_of.get(v, 0)]
ln = (0.7 + ((k * 5) % 9) / 6.0) * zm # 0.7 ~ 2.7 km
z[(u, v)] = (round(0.45 * ln / zb, 5),
round(0.30 * ln / zb, 5))
for i, t in enumerate(TIES):
tn = (t[0], t[1]) if t[0] < t[1] else (t[1], t[0])
z[tn] = (round(0.45 * 1.25 / zb, 5),
round(0.30 * 1.25 / zb, 5))
rng = random.Random(SEED)
tan_phi = math.tan(math.acos(0.95))
KL = 2.1
LS = [0.8, 1.0, 1.05, 1.3]
pat = {}
for ti, tg in enumerate(TRUNKS):
m = len(tg)
for idx, v in enumerate(tg):
pat[v] = (55.0 + 85.0 * math.sin(math.pi * idx / (m - 1))) \
* KL * LS[ti]
for (_, v) in LATERALS:
pat[v] = 70.0 * KL
P, Q = {}, {}
for v in sorted(pat):
p = pat[v] * (1.0 + rng.gauss(0.0, 0.06))
P[v] = round(p, 2)
Q[v] = round(p * tan_phi, 2)
return list(range(33)), tree, z, P, Q
def ne(e):
return (e[0], e[1]) if e[0] < e[1] else (e[1], e[0])
def _span(tree_edges):
"""从根 BFS:返回 adj/par/order;不连通或边数≠32 返回 None。"""
if len(set(ne(e) for e in tree_edges)) != 32:
return None
adj = {}
for (u, v) in tree_edges:
adj.setdefault(u, []).append(v)
adj.setdefault(v, []).append(u)
par = {ROOT: None}
order = [ROOT]
dq = [ROOT]
while dq:
u = dq.pop(0)
for v in adj.get(u, []):
if v not in par:
par[v] = u
order.append(v)
dq.append(v)
if len(order) != 33:
return None
return adj, par, order
def power_flow(tree_edges, z, P, Q):
"""前推回代潮流。返回 dict(loss_kW/vmin/vmin_node/iters) 或 None。"""
sp = _span(tree_edges)
if sp is None:
return None
adj, par, order = sp
down = list(reversed(order))
V = {n: V0 for n in order}
def zk(u, v):
return (u, v) if (u, v) in z else (v, u)
for it in range(1, MAXIT + 1):
cur = {n: 0j for n in order}
for n in down:
tot = 0j
if n != ROOT:
s_pu = complex(P[n] / 1000.0 / SB, Q[n] / 1000.0 / SB)
tot += complex(s_pu.real, -s_pu.imag) / V[n]
for c in adj.get(n, []):
if par.get(c) == n:
tot += cur[c]
cur[n] = tot
err = 0.0
newV = dict(V)
for n in order[1:]:
r, x = z[zk(par[n], n)]
vn = V[par[n]] - complex(r, x) * cur[n]
err = max(err, abs(vn - V[n]))
newV[n] = vn
V = newV
if err < TOL:
break
loss_kw = 0.0
for n in order[1:]:
r, _ = z[zk(par[n], n)]
loss_kw += (abs(cur[n]) ** 2) * r * SB * 1000.0
vm = {n: abs(V[n]) for n in order}
vmin, vmin_node = V0, ROOT
for n in order:
if n != ROOT and vm[n] < vmin:
vmin, vmin_node = vm[n], n
return dict(loss_kW=loss_kw, vmin=vmin, vmin_node=vmin_node,
iters=it)
def cycle_of(chord, tree_edges):
"""弦在辐射网上构成的基本环(树边集合)。不连通返回 None。"""
sp = _span(tree_edges)
if sp is None:
return None
par = sp[1]
def path_to_root(v):
ps = []
while v != ROOT:
ps.append((par[v], v))
v = par[v]
return ps
pa, pb = path_to_root(chord[0]), path_to_root(chord[1])
sa = set(pa)
common = None
for e in pb:
if e in sa:
common = e
break
cyc = []
for e in pa:
if e == common:
break
cyc.append(e)
for e in pb:
if e == common:
break
cyc.append(e)
return cyc
def eval_config(edges, z, P, Q):
pf = power_flow(edges, z, P, Q)
if pf is None:
return None
if pf["vmin"] < VMIN or pf["vmin"] > VMAX:
return None
return pf
def describe(cfg, base_tree):
"""把结构集合翻译成「断…投联络T…」操作描述。"""
base_set = {ne(e) for e in base_tree}
closed_set = {ne(e) for e in cfg}
opened = sorted(base_set - closed_set)
closed = []
for i, t in enumerate(TIES):
if ne(t) in closed_set:
closed.append("T%d(%d-%d)" % (i + 1, t[0], t[1]))
return "断" + "+".join("%d-%d" % o for o in opened) + \
" 投" + ",".join(closed)
def enumerate_single(z, P, Q, base_tree):
valid, invalid, total = [], 0, 0
for tie in TIES:
cyc = cycle_of(tie, base_tree)
for f in cyc:
total += 1
cfg = [e for e in base_tree if e != f] + [tie]
pf = eval_config(cfg, z, P, Q)
if pf is None:
invalid += 1
continue
valid.append(dict(open=f, tie=tie, loss=pf["loss_kW"],
vmin=pf["vmin"], vmin_node=pf["vmin_node"],
cfg=frozenset(cfg)))
valid.sort(key=lambda d: d["loss"])
return valid, invalid, total
def enumerate_two(z, P, Q, base_tree, singles):
"""双步顺序全枚举:全程归一化边,避免方向不一致导致删除失效。"""
full = [ne(e) for e in base_tree] + [ne(t) for t in TIES]
base_fs = frozenset(ne(e) for e in base_tree)
best, combos = None, 0
for s1 in singles:
tree1 = [ne(e) for e in s1["cfg"]]
in_cfg = set(tree1)
chords2 = [e for e in full if e not in in_cfg]
for ch in chords2:
cyc = cycle_of(ch, tree1)
if cyc is None:
continue
for f in cyc:
if ne(f) == ch:
continue
combos += 1
cfg = frozenset(ne(e) for e in tree1
if ne(e) != ne(f)) | {ch}
if cfg == base_fs:
continue
pf = eval_config(sorted(cfg), z, P, Q)
if pf is None:
continue
if best is None or pf["loss_kW"] < best["loss"]:
best = dict(loss=pf["loss_kW"], vmin=pf["vmin"],
vmin_node=pf["vmin_node"], cfg=cfg,
last_open=f, last_tie=ch)
return best, combos
if __name__ == "__main__":
nodes, base_tree, z, P, Q = build_system()
tp = sum(P.values())
tq = sum(Q.values())
base = power_flow(base_tree, z, P, Q)
print("== 系统 ==")
print("节点 33 支路 32 联络 5 总负荷 %.1f kW / %.1f kvar"
% (tp, tq))
print("== 基态潮流 ==")
print("网损 %.2f kW 网损率 %.2f%% 最低电压 %.4f pu @节点%d 迭代 %d 次"
% (base["loss_kW"], base["loss_kW"] / tp * 100.0,
base["vmin"], base["vmin_node"], base["iters"]))
print("== 单环枚举 ==")
singles, invalid, total = enumerate_single(z, P, Q, base_tree)
print("候选 %d 可行 %d 低电压剔除 %d" % (total, len(singles), invalid))
b1 = singles[0]
red1 = (base["loss_kW"] - b1["loss"]) / base["loss_kW"] * 100.0
print("单步最优 %s 网损 %.2f kW 降损 %.2f%%"
% (describe(b1["cfg"], base_tree), b1["loss"], red1))
print("== 双步枚举 ==")
b2, combos = enumerate_two(z, P, Q, base_tree, singles)
red2 = (base["loss_kW"] - b2["loss"]) / base["loss_kW"] * 100.0
red2b = (b1["loss"] - b2["loss"]) / b1["loss"] * 100.0
print("评估组合 %d 两步最优 %s 网损 %.2f kW 累计降损 %.2f%% 第二步再降 %.2f%%"
% (combos, describe(b2["cfg"], base_tree), b2["loss"], red2, red2b))
pf2 = power_flow(sorted(b2["cfg"]), z, P, Q)
print("重构后最低电压 %.4f pu @节点%d 提升 %.4f"
% (pf2["vmin"], pf2["vmin_node"], pf2["vmin"] - base["vmin"]))
print("== 负荷敏感性 ==")
for lv in (0.8, 0.9, 1.0, 1.1, 1.2):
Ps = {k: v * lv for k, v in P.items()}
Qs = {k: v * lv for k, v in Q.items()}
bl = power_flow(base_tree, z, Ps, Qs)["loss_kW"]
sg, _, _ = enumerate_single(z, Ps, Qs, base_tree)
t2, _ = enumerate_two(z, Ps, Qs, base_tree, sg)
print("负荷 %d%%: 基态 %.1f kW 重构 %.1f kW 降损 %.2f%%"
% (round(lv * 100), bl, t2["loss"],
(bl - t2["loss"]) / bl * 100.0))