ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

配电网重构实战:粒子群算法联合分层前推回代降线损

配电网重构实战:粒子群算法联合分层前推回代降线损 先聊一个很多做配网规划、配网自动化的人都绕不开的问题网架差不多、线路参数差不多但线损率就是比别人高几个百分点。很多时候问题不在设备上而在运行方式上——线路里哪些开关该合、哪些该分这个组合决定了潮流的走向也决定了网损的高低。配电网重构干的就是这件事在不改变网架物理结构的前提下通过改变分段开关和联络开关的分合状态找到一个能让网损最小、电压质量最好、负荷尽量均衡的运行方式。我落地这个课题时选的技术路线是“粒子群算法 分层前代回推潮流法”。粒子群负责在巨大的开关组合空间里搜解潮流计算负责评估每个候选方案的质量。这个组合在配电网重构里算是比较经典的搭配但经典不意味着简单真正写好、调通、跑出可信的结果中间有不少细节值得拿出来复盘。这篇就按我自己的实现思路把从问题建模到算法实现的完整过程拆开讲顺便把踩过的坑也一并列出来。1. 重构的本质不是优化设备是优化“运行方式”1.1 重构的数学形式与目标配电网重构在数学上可以描述成一个组合优化问题。状态变量是网络中各个开关的状态通常是二进制变量0代表断开1代表闭合。目标函数最常见的是最小化系统网损即[ \min P_{\text{loss}} \sum_{i \in E} R_i \cdot \frac{P_i^2 Q_i^2}{U_i^2} ]其中 E 是闭合支路的集合R_i 是支路电阻P_i 和 Q_i 是流过该支路的有功和无功功率U_i 是节点电压幅值。这个公式的思路很直接网损等于每条支路上电流平方乘电阻之和而电流又可以用复功率除以电压来近似表示。约束条件比目标函数更麻烦。首先是潮流约束也就是解出来的电压和功率分布必须满足电力系统的基本物理定律其次是节点电压上下限约束通常要求在 0.95 到 1.05 标幺值之间再次是支路容量约束也就是不能过载最后也是配网重构特有的约束重构后的网络必须保持辐射状结构既不能成环也不能产生孤岛。这最后一个约束是配电网和输电网很不一样的地方。输电网可以环网运行配电网为了保证故障隔离和继电保护的动作配合绝大多数情况下要求开环运行。也就是说解出来的开关组合对应的网络拓扑必须是一棵生成树。这个约束筛选掉了大量的候选解也是算法设计中最容易出问题的地方。1.2 为什么是粒子群而不是穷举或者其他算法开关组合的数量随开关数指数增长。一个中等规模的配网如果有 70 个可操作开关理论组合数是 2^70 量级穷举在工程上是不可能的。传统数学优化方法比如线性规划、非线性规划处理这类离散组合问题时要么建模困难要么求解效率低。粒子群算法的好处在于实现简单、参数少、收敛速度快而且对目标函数的梯度信息没有要求。潮流计算本身就是个迭代过程没法直接写成解析梯度给优化器用粒子群这种不依赖梯度、基于种群迭代的启发式算法就很合适。另外粒子群天然支持并行计算种群里的每个粒子都可以独立做潮流评估这在工程实现上非常友好——直接开多线程就能提速。相比之下遗传算法的编码和交叉变异操作在二进制开关编码上也不复杂但参数更多种群大小、交叉率、变异率都要调而且早熟收敛的问题往往比粒子群更明显。模拟退火一类的单点搜索算法虽然实现简单但全局搜索能力不足容易陷入局部最优。所以综合来看粒子群在这个场景下是性价比最高的选择。2. 粒子群算法在配电网重构里的工程化细节2.1 粒子的位置与速度在配电网里怎么定义标准粒子群算法里每个粒子代表解空间中的一个候选解粒子有位置和速度两个属性。位置代表当前解速度代表解在搜索空间中的移动方向和幅度。每轮迭代中粒子根据个体历史最优位置 pbest 和全局历史最优位置 gbest 来更新自己的速度再通过速度更新位置。在配电网重构这个离散场景里粒子位置直接编码为开关状态的二进制向量。向量长度等于网络中可操作开关的数量每个元素取 0 或 1。速度的更新公式保持不变[ v_{i,d}^{t1} w \cdot v_{i,d}^{t} c_1 r_1 (pbest_{i,d} - x_{i,d}^{t}) c_2 r_2 (gbest_{d} - x_{i,d}^{t}) ]但这里要做一个关键处理速度值是连续的而位置是离散的 0/1 值所以不能直接把速度累加到位置上。常用的做法是用变换函数把连续速度映射到 [0,1] 区间再通过概率判断位置取 0 还是取 1。最经典的是 Sigmoid 变换[ S(v) \frac{1}{1 \exp(-v)} ]然后用 S(v) 作为位置取 1 的概率生成一个 [0,1] 均匀分布的随机数 r如果 S(v) r位置取 1否则取 0。这就是二进制粒子群算法BPSO的核心逻辑。2.2 惯性权重、学习因子的调参心得二进制粒子群的参数设置直接决定搜索质量。惯性权重 w 控制的是粒子继承上一代速度的比例相当于全局搜索和局部搜索的平衡。w 大粒子飞得快搜索范围大但容易跳过最优解w 小局部搜索能力强但容易陷入局部最优。我实测下来线性递减策略比固定值好使从 0.9 逐步降到 0.4前期保持种群多样性、后期集中精细搜索。迭代次数设 100 到 200 代的话每代按固定步长递减即可。学习因子 c1 和 c2 分别控制向个体最优和全局最优靠拢的力度。标砖配置是 c1 c2 2但配电网重构这个场景里我建议 c1 稍大一点比如 2.0c2 稍小一点1.8 左右。原因是配网重构的解空间存在大量不可行解不满足辐射状约束如果 c2 太大整个种群会被快速拉向某个局部最优的可行解导致多样性迅速降低后面再想跳出去就很难了。速度限幅也很重要。Sigmoid 函数的曲线在速度绝对值大于 10 之后基本饱和也就是说速度再大对概率的影响也不大了。所以把速度限制在 [-4, 4] 这个区间就够用超出就截断。不然粒子速度无限增大Sigmoid 输出会长时间卡在 0 或 1 附近相当于位置被锁死丧失了搜索能力。2.3 实际操作中最容易翻车的点辐射状约束检查粒子群本身不管约束它只管按公式迭代生成的位置很可能是个带环的拓扑或者有孤岛的拓扑。如果不对这些不可行解做处理潮流计算根本没法收敛因为配电网的潮流算法包括我用的前推回代法前提就是辐射状单电源网络。约束处理有罚函数和修复两种思路。罚函数简单粗暴给不可行解一个很大的目标函数值把适应度压下去让粒子自己慢慢远离这些区域。但问题在于如果不可行解的占比太高种群里的粒子全在罚函数值的高地附近打转有效搜索效率很低运行一段时间后基本退化成了随机游走。我更推荐用修复策略。具体做法是粒子位置确定后先检查这个拓扑是否有环、是否有孤岛。通过图论连通性分析可以从根节点出发做广度优先搜索如果所有节点都能从根节点访问到且闭合支路数是节点数减一那么这个拓扑就是一棵合法的生成树。不满足时就随机断开环中的某些支路、把孤岛重新连回主网。这样既保证了粒子的可行性又不会完全破坏粒子原本的搜索方向。注意修复会消耗一部分计算时间但从实测效果看很值。我处理 IEEE 33 节点系统时未经修复的初始种群不可行率能到 30% 以上加上修复后几乎 100% 可行而且迭代收敛代数从 60 代降到 30 代左右。3. 分层前代回推潮流法比传统前推回代快在哪里3.1 前推回代法的核心思想配电网潮流计算和输电网很不一样。输电网是环网潮流方程是联立的大型线性方程组一般用牛顿-拉夫逊法求解。配电网是辐射状结构正好可以用前推回代法这种更简洁的迭代算法。前推回代法的原理分两步。回代从末端往根节点推已知各节点的负荷功率假设各节点电压初值为额定电压从网络末端开始逐层向前计算每条支路的功率损耗和流过的功率。前推从根节点往末端推已知电源节点一般是变电站母线的电压结合支路流过的功率逐层向后计算每个节点的电压降更新节点电压。反复交替执行前推和回代直到两次迭代的节点电压差小于设定的收敛阈值。这个方法对辐射状网络特别友好收敛速度快数值稳定性好对初值不敏感。但传统实现有个问题每次迭代都要遍历整个网络。节点少还没什么节点到了几百上千个计算开销就上来了。3.2 层次化建模把树状网络变成有层级的结构分层本质上是对网络拓扑做一次预处理让每个节点知道自己处于整个树形结构的第几层。具体做法是从电源节点根节点开始做广度优先搜索先给根节点标层级 0然后根节点的子节点标注层级 1子节点的子节点标注层级 2依此类推直到所有节点都有层级编号。有了层级信息后前推回代就可以按层序遍历而不是按节点编号遍历。回代时从最大层级的节点开始逐层向根节点推进前推时从层级 1 开始逐层向末端推进。这样做的好处是同一层级的节点之间没有从属关系它们的计算互不依赖天然适合并行计算而且因为事先构建了父子关系邻接表不需要每次迭代都做网络拓扑搜索。我在实现时用字典结构来存储分层结果每个节点关联自己的父节点、子节点列表和层级号。计算前先构建这个结构之后每次潮流计算都直接查字典省去了反复遍历邻接矩阵的昂贵操作。对于每次粒子群迭代都要调用上千次潮流评估的场景这个预处理省下来的时间非常可观。3.3 与粒子群耦合的两种模式粒子群和潮流的耦合方式直接决定程序的整体效率。第一种是串行模式每个粒子做完潮流评估后再轮到下一个粒子做程序逻辑简单清晰。第二种是批量模式把一整代粒子打包用多线程或向量化的方式同时做潮流计算。工程上强烈建议后者因为粒子之间完全没有依赖关系天然可并行。我自己的实现里用了 Python 的 multiprocessing 进程池把粒子按批次提交每个进程独立做分层前推回代计算。在 4 核机器上跑实测加速比接近 3.8 倍基本是线性扩展。如果后续要处理更大规模网络还可以改用 GPU 做批量潮流计算但这个对于一般项目来说暂时还用不上。4. 完整的重构流程与核心代码实现4.1 整体流程分为哪几步配电网重构的完整流程可以拆成以下几个阶段。第一阶段是数据准备读取网络拓扑、线路参数、负荷数据、开关初始状态。第二阶段是算法初始化设置粒子群参数生成初始种群并对每个粒子做拓扑修复确保所有初始解都是可行的辐射状网络。第三阶段是迭代优化每轮迭代做三件事对每个粒子做潮流评估更新粒子的个体最优和全局最优根据更新公式调整粒子的速度和位置对位置更新后产生的不可行拓扑做修复。第四阶段是结果输出提取全局最优解对应的开关组合、网损值和各节点电压做进一步分析。这个流程里最关键的技术决策是潮流评估放在粒子群迭代内部每次迭代都要调用种群规模次数的潮流计算。所以潮流算法的性能直接决定整个优化过程能不能在可接受的时间内跑完。4.2 核心代码粒子群部分下面这段是二进制粒子群核心更新逻辑的简化版代码完整实现里还需要加上拓扑修复和潮流模块class BPSO: def __init__(self, n_switches, n_particles, max_iter, w_max, w_min, c1, c2): self.n_switches n_switches self.n_particles n_particles self.max_iter max_iter self.w_max w_max self.w_min w_min self.c1 c1 self.c2 c2 def sigmoid(self, v): # 限制 v 范围防止 exp 溢出 v np.clip(v, -8, 8) return 1.0 / (1.0 np.exp(-v)) def run(self, fitness_func): # 初始化位置和速度0-1 随机初始化 x np.random.randint(0, 2, (self.n_particles, self.n_switches)) v np.random.uniform(-4, 4, (self.n_particles, self.n_switches)) pbest_x x.copy() pbest_fit np.array([fitness_func(ind) for ind in x]) gbest_idx np.argmin(pbest_fit) gbest_x pbest_x[gbest_idx].copy() gbest_fit pbest_fit[gbest_idx] for t in range(self.max_iter): w self.w_max - (self.w_max - self.w_min) * t / self.max_iter r1 np.random.random((self.n_particles, self.n_switches)) r2 np.random.random((self.n_particles, self.n_switches)) v (w * v self.c1 * r1 * (pbest_x - x) self.c2 * r2 * (gbest_x - x)) v np.clip(v, -4, 4) prob self.sigmoid(v) x (np.random.random((self.n_particles, self.n_switches)) prob).astype(int) # 每个粒子做拓扑修复保证辐射状 for i in range(self.n_particles): x[i] repair_topology(x[i]) # 评估适应度 cur_fit np.array([fitness_func(ind) for ind in x]) update_idx cur_fit pbest_fit pbest_x[update_idx] x[update_idx] pbest_fit[update_idx] cur_fit[update_idx] best_idx np.argmin(pbest_fit) if pbest_fit[best_idx] gbest_fit: gbest_fit pbest_fit[best_idx] gbest_x pbest_x[best_idx].copy() return gbest_x, gbest_fit这段代码有几点要额外说明。位置初始化用的是均匀随机分布但如果网络规模比较大推荐用“全网闭合后随机断开支路”的方式生成初始解这样初始粒子多为连通网络能减少修复的工作量。速度初始化范围定为 [-4, 4]这是为了保证初始阶段 Sigmoid 函数的输出不完全饱和粒子在早期能充分探索解空间。4.3 核心代码分层前推回代潮流部分分层前推回代的实现分成网络分层和潮流迭代两个子模块。网络分层通过广度优先搜索完成from collections import deque def build_layers(root_node, adj_list): adj_list: 字典每个节点对应的相邻节点闭合支路列表 返回: layer_of_node 字典父节点字典子节点字典 visited {root_node: 0} parent {} children {n: [] for n in adj_list.keys()} layers [] q deque([root_node]) while q: node q.popleft() layer visited[node] if len(layers) layer: layers.append([]) layers[layer].append(node) for nxt in adj_list[node]: if nxt not in visited: visited[nxt] layer 1 parent[nxt] node children[node].append(nxt) q.append(nxt) return visited, parent, children, layers有了层级结构前推回代就非常简单了def power_flow(load_p, load_q, line_r, line_x, root_node, base_voltage10.0): # load_p / load_q: 节点负荷 # line_r / line_x: 支路电阻/电抗 # 先构建分层结构这里假设已传入 visited, parent, children, layers build_layers(root_node, adj_list) n len(load_p) V np.ones(n) * base_voltage S np.zeros(n, dtypecomplex) # 节点注入复功率 for _ in range(max_iter): V_old V.copy() # 回代从最深层向根节点推功率 for layer in range(len(layers)-1, 0, -1): for node in layers[layer]: p_node load_p[node] real(S[node]) q_node load_q[node] imag(S[node]) # 累加到父节点支路的功率 if node in parent: p_parent parent[node] # 计算从父节点流向 node 的功率 # 这里简化支路功率 节点累积功率 支路损耗 branch_p p_node branch_q q_node branch_r line_r[(parent[node], node)] branch_x line_x[(parent[node], node)] loss_p branch_r * (branch_p**2 branch_q**2) / (V[node]**2) loss_q branch_x * (branch_p**2 branch_q**2) / (V[node]**2) S[p_parent] (branch_p loss_p) 1j * (branch_q loss_q) # 前推从根节点向末端推电压 for layer in range(1, len(layers)): for node in layers[layer]: p_parent parent[node] branch_r line_r[(p_parent, node)] branch_x line_x[(p_parent, node)] p_flow real(S[node]) load_p[node] q_flow imag(S[node]) load_q[node] V[node] V[p_parent] - (branch_r * p_flow branch_x * q_flow) / V[p_parent] if np.max(np.abs(V - V_old)) epsilon: break return V简化版代码里省略了一些细节处理但核心流程已经能看清楚。前推回代对初值不敏感一般 4 到 6 次迭代就能收敛比牛顿法动不动十几次的迭代强很多。计算网损时等潮流收敛后再遍历所有闭合支路把每条支路的损耗累加起来即可。5. 实际测试数据与调参过程5.1 用 IEEE 33 节点验证重构效果我在实际验证时用的是 IEEE 33 节点标准算例这是配电网重构领域最经典的测试系统。它包含 32 条常闭支路和 5 条常开联络开关基准网损约为 202.68 kW这个数字在很多文献里都能对得上。优化目标是找到 5 组开关位置的调整组合使得网损最小。用标准参数跑完 100 代后找到了最优开关组合闭合 5 条联络开关中的 4 条具体编号是 33、34、35、36同时断开原有的 4 条分段开关。优化后网损降到约 139.5 kW降幅超过 31%。这个结果和文献中报道的经典重构值接近说明粒子群分层前推回代的组合确实能稳定找到高质量解。除了网损下降重构后各节点电压水平也有明显改善。重构前最低节点电压约为 0.903 标幺值已经低于 0.95 的下限重构后最低电压提高到 0.938 左右虽然还不能完全满足要求但改善幅度明显。实际工程里重构能解决一部分电压问题但电压偏差过大时还是需要配合无功补偿或调压设备一起处理。5.2 参数对结果的影响和调整记录我跑了好几组对照实验把参数影响总结成一张表参数设置过小的影响设置过大的影响推荐范围惯性权重 w收敛慢种群多样性差收敛快但容易早熟漏掉最优解0.4 ~ 0.9线性递减学习因子 c1粒子缺乏个体探索动力过于强调个体经验群体协作变弱c1 2.0学习因子 c2收敛慢群体过度集中陷入局部最优c2 1.8 左右种群大小搜索覆盖不足计算量剧增收益递减30 ~ 50速度限幅位置更新受限Sigmoid 饱和搜索停滞[-4, 4]种群大小方面33 节点这种小系统 30 个粒子就够用了跑 100 代总潮流评估次数在 3000 次左右每代用时不到 1 秒整体优化时间在 1 分钟内。但如果是 200 节点以上的馈线建议把种群调到 60 ~ 80同时配合并行评估这样才能在计算资源和搜索质量之间取得平衡。6. 实测踩过的坑与排查技巧实录6.1 网损不收敛问题往往在拓扑修复我第一次跑通整套代码时最棘手的问题是部分粒子对应的网络拓扑虽然看起来是连通的但实际上存在“伪环”——网络在物理上形成了环路只是节点编号上不明显。这种情况下前推回代法会不断迭代但无法收敛或者收敛到错误的结果。后来我在每次潮流计算前都增加了一步割集检测计算闭合支路数是否等于节点数减一。如果闭合支路数多了说明存在环少了说明存在孤岛。这个判断在数学上等价于生成树的条件实现简单效果立竿见影。加了这步之后潮流计算的不收敛率从接近 10% 降到了 0。6.2 粒子群早熟收敛本地问题用本地解另一个常见问题是在复杂网络上粒子群早早收敛到局部最优网损停滞在某个值附近跳不出来。排查后发现是因为惯性权重衰减太快种群在前 20 代就高度聚集在某个区域。解决方法有三个从简到繁一是把惯性权重下限从 0.4 提高到 0.5让粒子在中后期仍然有一定的探索能力二是引入变异操作每代随机选 5% 的粒子对它们的位置做随机扰动三是用多种群策略把粒子分成几个子群各自独立进化每过若干代再交换一次最优解。实际工程里做第一个和第二个就足够应对大多数情况了。6.3 计算性能瓶颈及我的优化方案如果网络规模上来之后性能瓶颈主要集中在两个地方潮流求解和拓扑修复。潮流求解用并行评估解决前面已经提到。拓扑修复的瓶颈在于每次都做全图遍历我后来改成增量式修复只对粒子位置发生变化的区域做连通性修正大幅减少了重复计算。对于 300 节点左右的网络这个优化能再省 30% 到 40% 的迭代时间。另外有一个工程细节容易被忽略前推回代法对浮点误差的累积在一定规模后会比较明显节点电压的精度会下降。我的做法是提高计算精度到 float64并在每次迭代后强制对电压做一次平滑处理限制相邻迭代间电压变化量。这个操作让最终网损计算结果更稳定也能减少粒子群迭代时目标函数值的“抖动”。6.4 实际运行中对不可行解的处理建议修复策略对比罚函数策略的优势可以量化为在 33 节点算例上纯罚函数算法约 35% 的计算时间花在评估不可行解上而且最终网损优化结果平均比修复策略高 8% 到 12%。这个差距在更复杂的网络上会被进一步放大。所以我的建议是优先实现设计良好的修复策略罚函数只作为最后兜底手段——当修复本身会消耗过多算力或导致原始解失真太严重时再考虑用它。修复时的注意点是别为了追求可行性把粒子本身的结构破坏得太厉害。比如某个粒子已经找到了一组接近最优的开关组合但因为在某个支路上形成了小环强制断开环中支路的时候如果随机挑一条断开可能会把已经很好的解破坏掉。更稳妥的做法是优先断开负荷较轻的支路让网损损失最小。7. 一些经验层面的思考与扩展做配电网重构项目这段时间我最深的体会是优化算法只是框架真正决定工程效果的是问题建模的准确性和细节处理的细致程度。粒子群的代码在网上能找到很多版本但适配到配电网重构这个场景关键差异全在拓扑约束处理、潮流计算耦合方式、以及不可行解修复策略这些细节上。这个方案可以扩展的方向不少。一是把目标函数从单一网损扩展成多目标优化同时考虑网损、电压偏差和开关操作次数用带权重的线性加权或者直接上多目标粒子群优化算法二是在约束里引入分布式电源接入后的影响DG 的出力波动会让重构结果对时序敏感需要在目标函数里增加风险项三是结合配网自动化的实时数据把重构从离线计算变成准实时决策这对算法的单次求解速度要求就更高了可能需要考虑简化模型或者在线学习的方法。对刚开始接触这个方向的朋友我建议不要一上来就追求复杂算法先把前推回代潮流写对、把辐射状约束检查写对再套粒子群框架这样调试起来会省非常多时间。跑通了标准算例再往分布式电源、多目标、动态重构这些方向去扩展思路会清晰很多。
返回列表