ARTICLE DETAIL

资讯详情

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

改进二进制粒子群算法在配电网重构中的Matlab复现与调试

改进二进制粒子群算法在配电网重构中的Matlab复现与调试 如果你跟我一样第一次拿到《改进二进制粒子群算法在配电网重构中的应用【核心论文复现】》这个题目时第一反应多半是找一份现成的Matlab代码跑通然后把结果图贴上去交差。但真正动手复现过的人都知道这条路远没那么顺畅——IEEE33节点的原始数据要自己整理前推回代潮流算法要自己写更麻烦的是论文里那句“引入自适应变异策略”到底改在哪一个环节、怎么改作者通常不会写得特别细。这篇文章我就把完整的复现思路过一遍包括标准二进制粒子群算法的缺陷、改进点在哪个环节生效、Matlab代码怎么组织以及我在调试过程中踩过的几个非常隐蔽的坑。适合有粒子群基础但没做过配电网重构的初学者也适合刚接到类似复现任务的研一学生。1. 配电网重构到底在解决什么问题1.1 一个看似简单却影响巨大的运行优化问题很多人第一次接触“重构”这个词会误以为是要把线路重新排布一遍或者把物理网架改造一下。实际上配电网重构是一个纯调度层面的操作现有的网架结构不变只是通过改变分段开关和联络开关的开合状态来改变负荷的供电路径。配电网正常运行时采取“闭环设计、开环运行”的模式。变电站出口到每个用户之间只有一条默认的供电路径路径上串联着一系列常闭的分段开关。网络中同时还布设了若干常开的联络开关平时不参与供电但一旦某条线路检修或故障联络开关闭合就能把负荷从相邻馈线转移过来。重构做的事情本质上就是重新回答一个问题在满足安全约束的前提下哪些开关应该闭合、哪些应该打开才能让功率从电源到负荷的输送路径最合理。为什么改变开关状态就能降低损耗道理不复杂。线路损耗和流经它的功率平方成正比和节点电压平方成反比。如果能把一部分负荷从一条又长又重载的线路上转移到另一条更短、更轻载的线路上整体损耗自然就降下来了。哪怕只是把某个负荷调整到电压更高的母线上供损耗也会有可观的改善。这就是重构最直接的价值不花一分钱设备投资纯粹靠调整运行方式就能实现降损增效。1.2 目标函数与约束条件的数学表达把这个问题写成数学形式就是一个带约束的组合优化问题。目标函数通常取系统总有功损耗最小$$P_{loss} \sum_{i1}^{b} R_i \frac{P_i^2 Q_i^2}{V_i^2}$$其中$R_i$是第i条支路的电阻$P_i$和$Q_i$是流过该支路的有功和无功功率$V_i$是支路末端电压。注意这个公式隐含了一个重要关系电压越低、功率越大损耗就越高。所以重构算法如果能找到一组开关状态让全网电压水平抬高、让功率尽量走短路径网损就能明显下降。约束条件有四个一个都不能少潮流约束重构后的网络必须满足基尔霍夫定律也就是说要重新做一次潮流计算得到真实的电压、功率分布。节点电压约束每个节点电压通常要求维持在0.95到1.05 pu之间超过这个范围就认为不可行。支路容量约束流过每条支路的视在功率不能超过导线载流量上限。辐射状拓扑约束重构后的网络必须保持无环、连通也就是任何一个负荷节点都能由电源节点供电且所有闭合支路不能构成回路。这四个约束的处理方式直接决定了算法的收敛速度和最终质量。常见的做法是把电压约束和辐射状约束转化成惩罚项加到适应度函数里让不可行解的适应度值变差粒子在迭代过程中自然会避开它们。但这个策略有个隐患后面我会专门讲。1.3 为什么这个问题的求解难度远超你的直觉IEEE33节点系统有37条支路每条支路的开关状态是0或1理论上搜索空间是$2^{37}$大约1370亿种组合。虽然辐射状约束和连通性约束会排除掉绝大部分组合但剩下的可行解数量依然是一个天文数字。这是一个典型的NP-hard组合优化问题传统枚举法在稍大一点的系统上就彻底失效。这个问题的难点还在于开关状态和网损之间不是单调关系。闭合某条联络开关可能形成环路导致潮流暴增但也可能恰好分流了重载区段的负荷反而降低网损。换句话说这是一个存在大量局部极值的多峰优化问题。对算法来说既要保证搜索的多样性避免困在某个局部最优解里又要保持一定的收敛速度不能在大范围内漫无目的地乱跳。这种“既要又要”的需求恰恰是粒子群算法这类智能算法发挥优势的舞台也给“改进算法”留下了大量论文产出的空间。2. IEEE33节点系统为什么几乎所有论文都在用它2.1 经典算例的基本参数IEEE33节点标准测试系统最早由Baran和Wu在1989年提出后来成了配电网重构、分布式电源优化配置、无功优化等方向最常用的基准算例。系统有33个节点、37条支路基准电压12.66 kV基准功率通常取10 MVA总负荷约3715 kW加2300 kvar。系统结构可以这样理解节点1是变电站出口也就是平衡节点整个系统从节点1分出两条长馈线一条往节点18方向延伸一条往节点22方向延伸中间又分出若干分支线。除了主馈线之外还有三组支路挂在节点8、节点3和节点6下面整体结构比单纯的一条链要复杂一些但规模又不会大到让算法验证变得困难。2.2 分段开关与联络开关的分工37条支路中前32条是分段开关初始状态全部闭合构成从电源到各负荷点的供电路径最后5条是联络开关初始状态全部断开分别是8-21、9-15、12-22、18-33、25-29。这5条联络开关很有意思每一条闭合后都会与已有的分段开关路径构成一个基本环路所以这个系统的环路结构非常清晰闭合5条联络开关就会产生5个基本环。重构的物理含义也就在这初始状态下5个断开的支路恰好是5条联络开关。通过算法调整后断开的5条支路可以是“4条分段开关加1条联络开关”也可以是“3条分段开关加2条联络开关”但总断开数量必须保持为5。为什么因为33个节点要维持辐射状连通闭合支路数必须正好是32而总支路数是37所以断开支路数只能是5。这个“5”是图论给出的硬约束不管怎么重构都必须遵守。2.3 基准结果判断复现成败的标尺IEEE33节点系统有一个非常重要的“标准答案”初始状态下系统总网损约202.67 kW最低节点电压出现在节点18约为0.9131 pu。任何一篇重构论文、任何一份复现代码如果初始网损不是这个数值说明你的潮流计算或数据录入一定出错了。这是整个复现项目第一个必须对上的校验点。经过重构后的经典最优结果文献中反复出现的是断开7-8、9-10、14-15、32-33、25-29这5条支路其中前4条原本是分段开关最后一条原本是联络开关。此时网损降到约139.55 kW最低电压抬升到约0.9378 pu。注意这个结果具体数值会因潮流计算精度、基准功率选取的细微差别有少许出入但大差不差通常都在139.5到140 kW之间。如果你的算法跑到这个量级基本可以认为复现是成功的。状态断开支路网损(kW)最低电压(pu)初始状态8-21、9-15、12-22、18-33、25-29202.670.9131经典重构最优7-8、9-10、14-15、32-33、25-29139.550.93783. 从标准PSO到二进制PSO0/1变量怎么优化3.1 粒子群优化的核心逻辑回顾标准粒子群算法模拟鸟群觅食行为每个粒子是一个候选解在解空间中飞行。粒子有三个核心要素当前位置、当前速度、历史最优位置。每个粒子的速度更新公式如下$$v_{i,j}(t1) w \cdot v_{i,j}(t) c_1 r_1 [pbest_{i,j} - x_{i,j}(t)] c_2 r_2 [gbest_j - x_{i,j}(t)]$$然后位置更新就是$$x_{i,j}(t1) x_{i,j}(t) v_{i,j}(t1)$$这三项各司其职惯性项让粒子保持原有运动趋势保持探索能力个体认知项把粒子拉向自己历史最优位置社会项把粒子拉向群体最优位置。学习因子c1和c2决定了个体经验和社会经验的影响力随机数r1和r2提供随机性惯性权重w则负责平衡全局搜索和局部开发。我在给组里同学讲这个算法的时候喜欢用一个生活类比粒子就是一群外卖骑手pbest是自己摸索出来的最优取餐路线gbest是所有骑手共享的最优路线速度则相当于骑手当前的骑行趋向。最后的路线一定是个人经验和群体经验的折中而且还带着点惯性——以前骑得快下一时刻大概率还是快。3.2 连续PSO直接套用会卡在哪配电网重构的决策变量是开关状态0代表打开1代表闭合。这是一个典型的离散组合优化问题。如果直接把标准PSO拿来用粒子位置的每一维是连续值比如某个粒子在第5维的位置是3.72这个数到底代表开关断开还是闭合没法解释。当然可以做一个简单映射比如大于0.5就是闭合小于0.5就是断开。但这样做有个致命问题连续位置更新在0.49和0.51之间来回跨越时对应开关状态会频繁跳变算法根本稳定不下来而且连续位置的大小差异没有意义——3.72和0.51都只是代表“闭合”中间的距离信息全部丢失了。用标准PSO跑这个问题的效果往往会非常差。3.3 二进制粒子群的巧妙之处速度变成概率改进的思路一句话就能概括不再让速度直接决定位置而是让速度决定位置取1的概率。具体做法是把速度带入sigmoid函数得到一个0到1之间的概率值然后用随机数决定最终位置$$S(v_{i,j}) \frac{1}{1 e^{-v_{i,j}}}$$$$x_{i,j} \begin{cases} 1 \text{if } rand() S(v_{i,j}) \ 0 \text{otherwise} \end{cases}$$这个操作的含义非常优雅速度越大位置取1的概率越接近1速度越小取1的概率越接近0速度接近0的时候概率是0.5粒子完全随机地决定0还是1。这就完成了一个从“连续速度空间”到“离散位置空间”的概率映射让粒子仍然按照PSO的速度-位置更新框架飞行但最终产生的是0/1决策向量。这个映射有几个值得注意的地方。第一sigmoid函数在速度绝对值较大时会出现饱和速度从10增加到100概率几乎没有变化这就导致算法后期粒子“飞不动”了。第二位置更新是概率性的同一个速度值在不同次更新中可能产生不同的位置结果这给算法增加了随机探索性但也会导致收敛后粒子还在跳变。第三每一维开关是独立更新的粒子无法感知37维之间的拓扑耦合关系这就意味着算法必须依赖适应度函数来隐式地学习“哪些开关组合是可行的”学习成本相当高。4. 论文改进的核心思路解决标准BPSO的三个痛点4.1 痛点一惯性权重固定导致后期收敛乏力标准BPSO最让人头疼的问题之一就是惯性权重w的选取。w太大粒子惯性过强容易在最优解附近来回震荡难以精细逼近w太小粒子又过早失去探索新区域的能力陷入局部最优。最有效的做法是让w随迭代次数动态变化。我复现的这类改进论文中典型方案是采用非线性递减策略比如按迭代次数的幂次衰减$$w(t) w_{max} - (w_{max} - w_{min}) \cdot \left(\frac{t}{T}\right)^2$$其中$w_{max}$取0.9$w_{min}$取0.4T是最大迭代次数。这种二次衰减策略让w在前期保持较大值粒子能在大范围内搜索保证多样性后期w快速下降粒子逐渐转向精细开发。相比线性递减这种非线性策略在配电网重构这类多峰问题上往往能多找到几个更优的解。4.2 痛点二粒子容易早熟需要引入变异算子标准BPSO另一个被诟病的点是一旦某个粒子找到局部最优其他粒子会被快速吸引过去群体的位置多样性迅速下降。更麻烦的是配电网重构问题中网损函数对开关组合高度敏感一个开关状态不同可能产生完全不同的潮流分布粒子一旦聚集基本就丧失了跳出局部极值的能力。改进方案是借鉴遗传算法的变异思想。每次迭代更新完粒子位置后以一定概率pm对粒子的若干维度执行翻转操作也就是0变1、1变0。关键是变异概率和变异维度的选取。pm取太大会破坏收敛性让粒子在最优解附近反复横跳取太小又起不到作用。我实测下来pm取0.05到0.1之间比较合适并且随着迭代进行可以适当降低。变异的维度数也要控制一般每次随机选1到3维翻转既能产生新的拓扑结构又不会把粒子破坏得太离谱。这里有一个论文里常写但实际不好复现的细节变异操作的时机。我建议在更新pbest和gbest之前执行变异让变异后的新位置参与适应度评估这样变异质量的好坏会直接影响粒子的历史最优更新有效变异才能被保留下来。4.3 痛点三辐射状约束处理不得当会让算法做无用功这是整个复现中最关键、也最容易翻车的部分。IEEE33节点的37维开关状态任意随机生成一个0/1向量大概率是“非辐射状”或“不连通”的可能某些节点形成孤岛也可能支路构成环路。如果对每个不可行解都只靠惩罚项拉回算法会把大量计算资源浪费在无效解的评估上收敛速度惨不忍睹。改进论文里常见的做法是环路编码与拓扑修复相结合。我的理解是这样的既然IEEE33节点闭合一个联络开关就形成一个基本环那么每个基本环上必须且只能有一个断开的开关这样至少能保证局部无环同时再用图论方法检查全局连通性确保没有孤岛。具体到实现上可以先把37条支路按基本环分组每个基本环包含一个联络开关和若干分段开关。粒子更新后检查每个基本环中开关状态为0的个数如果一个环里断开开关数是0就随机打开该环中某个分段开关如果一个环里断开开关数大于1就随机闭合多余的断开支路让该环只保留一个断点。这个修复操作配合连通性检查能显著提高可行解比例。我在实现中经过这个修复后粒子群中可行解的比例能从不到20%提升到80%以上效果非常明显。4.4 改进算法完整流程综合以上三个改进点整个改进BPSO的流程可以整理成下面这张清单初始化读取IEEE33节点数据设置粒子数N通常40、最大迭代次数T通常100、学习因子c1c22、惯性权重范围0.4到0.9。粒子编码每个粒子是37维0/1向量对应37条支路开关状态初始时随机生成但要保证网络连通且辐射状。适应度评估对每个粒子做前推回代潮流计算算网损叠加电压越限惩罚和拓扑不可行惩罚。更新个体最优pbest和全局最优gbest记录收敛历史。按非线性递减公式更新惯性权重w。按标准速度-位置公式更新粒子速度sigmoid映射生成新的位置向量。对每个粒子执行自适应变异按概率pm随机翻转少量维度。执行环路约束修复保证每个基本环有且只有一个断开支路并做连通性检查。重复3到8步直到达到最大迭代次数或gbest连续20代不变化。输出gbest对应的断开支路组合、网损、最低电压、收敛曲线。5. Matlab代码实现从数据准备到结果验证5.1 数据准备与编码映射第一件事是把IEEE33节点的标准数据整理成Matlab能用的格式。我习惯用两个结构体BranchData和NodeData。BranchData每行存储一条支路的信息包括起始节点、终止节点、电阻、电抗NodeData存储每个节点的有功负荷和无功负荷。开关状态的编码映射是无数人出错的地方。我的做法是定义SwitchState为37维向量第1到37维分别对应BranchData中的第1到37条支路1代表闭合0代表断开。这个映射关系必须和支路编号严格一致否则后面所有计算都是错的。调试的时候先用这个SwitchState跑一次初始状态也就是前32维全1、后5维全0如果潮流计算出的网损是202.67kW说明映射大概率没问题。5.2 前推回代潮流计算模块配电网是辐射状网络用牛顿-拉夫逊法当然可以算但实现复杂迭代收敛性有时反而不好。前推回代法是配电网最经典的潮流算法原理直观先假设各节点电压为额定值从末端节点向根节点逐级计算支路功率这叫前推然后从根节点向末端节点逐级更新节点电压这叫回代。两个过程反复迭代直到相邻两次迭代的电压差小于收敛阈值。核心代码结构大致如下function [V, Ploss] powerflow(BranchData, NodeData, SwitchState) % 根据SwitchState提取导通支路 activeIdx find(SwitchState 1); % 节点编号和支路参数都按拓扑顺序组织 V ones(33, 1); % 初始电压幅值归一化 for iter 1:500 V_old V; % 前推从末端向根节点求支路功率 % 支路视在功率 末端节点负荷 下游支路功率之和 % 回代从根节点向末端求电流和电压降落 % V_child V_parent - I * (R jX) if max(abs(V - V_old)) 1e-6 break; end end % 网损 所有支路损耗之和 Ploss sum(real((P_branch.^2 Q_branch.^2) ./ V.^2 .* R)); end写这段代码时有一个必须重视的细节前推回代要求支路数据按“从根到末端”的方向组织或者通过节点分层来动态识别上下游关系。我一个朋友复现时支路数据顺序反了前推回代一直不收敛浪费了一整天。建议先用初始状态跑一下观察各节点电压是否沿馈线逐渐下降节点18电压是否约为0.9131pu这是验证潮流程序正确的金标准。5.3 适应度函数与约束惩罚有了潮流计算适应度函数就可以定义了。目标函数是网损最小同时要对电压越限和拓扑不连通施加惩罚。我的适应度代码大致如下function fit fitnessFunction(switchState, BranchData, NodeData) % 先做连通性检查使用图遍历快速判断是否所有节点可达 if ~checkConnectivity(switchState, BranchData) fit 1e6; % 直接给一个很大的惩罚值不浪费算力跑潮流 return; end % 做潮流计算 [V, Ploss] powerflow(BranchData, NodeData, switchState); % 电压越限惩罚 voltagePenalty 1000 * sum((min(V, 1.05) - 1.05).^2 (max(V, 0.95) - 0.95).^2); % 辐射状检查闭合支路数必须等于节点数减1且连通 if sum(switchState) ~ 32 || ~isRadial(switchState, BranchData) fit Ploss 500; else fit Ploss voltagePenalty; end end注意连通性检查必须放在潮流计算之前这一步能省下大量无效潮流计算。我见过有些复现代码把连通性检查放在潮流之后粒子生成的大量不可行解都要先跑一遍潮流才发现根本不连通整个实验跑下来耗时增加好几倍。惩罚系数的取值也很讲究我试过100到10000之间的不同值最终取1000对电压越限比较合适太小了约束形同虚设太大了又会让粒子为了满足电压约束而牺牲太多搜索自由度。5.4 改进BPSO主循环主循环是整个算法的骨架我一般按下面的框架组织% 初始化参数 N 40; T 100; c1 2; c2 2; wmax 0.9; wmin 0.4; % 初始化粒子群 X initParticles(N); % N个37维0/1向量 V zeros(N, 37); pbest X; pbestFit inf(N, 1); for iter 1:T % 非线性递减惯性权重 w wmax - (wmax - wmin) * (iter / T)^2; for i 1:N % 速度更新 V(i,:) w * V(i,:) c1*rand(1,37).*(pbest(i,:) - X(i,:)) ... c2*rand(1,37).*(gbest - X(i,:)); % 位置更新sigmoid概率映射 X(i,:) (rand(1,37) 1 ./ (1 exp(-V(i,:)))); % 自适应变异 if rand() pm mutIdx randperm(37, randi([1,3])); X(i, mutIdx) 1 - X(i, mutIdx); end % 环路约束修复 连通性检查 X(i,:) repairRing(X(i,:)); % 适应度评估 fit fitnessFunction(X(i,:), BranchData, NodeData); if fit pbestFit(i) pbest(i,:) X(i,:); pbestFit(i) fit; end end [bestFitThis, bestIdx] min(pbestFit); if bestFitThis gbestFit gbestFit bestFitThis; gbest pbest(bestIdx, :); end histBest(iter) gbestFit; end这个主循环里有几个细节值得展开说一下。变异操作中的randperm是从37维中挑出变异的维度每次挑1到3个这个随机维度数让变异的破坏力度有弹性回暖能力比固定变异1维好很多。适应度评估放在修复之后保证每个粒子都是以合法拓扑的身份参与评价。gbest的更新放到了所有粒子评估完再统一做避免在同一代内用未更新完的信息干扰其他粒子的速度更新。5.5 结果验证复现成功与否看这几个指标算法跑完不能只看网损一个数。我总结了一套完整的验证清单第一初始网损校验。拿初始开关状态跑一遍潮流网损必须是202.67kW左右最低电压0.9131pu左右。这两项对不上后面的所有结果都没有意义。第二重构后网损对比。改进BPSO跑出来的最优网损应该在139.5kW附近。如果只能跑到145kW以上大概率是改进策略没有生效或者惩罚系数不合适。第三收敛曲线形态。画出gbest随迭代次数的变化曲线改进算法应该呈现“前期快速下降、中期缓慢优化、后期趋于平稳”的形态。如果曲线在20代之前就完全水平了说明陷入早熟。第四多次独立运行对比。智能算法有随机性单次结果不能说明问题。我一般每个参数配置跑20次记录最优值、平均值、最差值。改进BPSO相比标准BPSO不光是最好值要更好平均值也要明显更优这才说明算法稳定性提升了不是靠运气撞出来的。6. 复现中的坑与调试经验6.1 第一个坑数据文件里的负荷方向搞反了IEEE33节点数据在网上流传的版本很多有的写成负荷功率是“从支路末端流出”有的写成“从节点注入”如果直接拿别人整理的Excel数据用很容易把负荷加错节点。我的调试经验是先打印出每个节点的功率和电压分布确认功率都是从根节点流向末端电压从节点1开始沿馈线逐渐下降。如果某个末端节点电压比首端还高必然是该节点负荷符号反了。6.2 第二个坑连通性检查用错了算法用图论方法检查连通性最直接的就是广度优先搜索或深度优先搜索从节点1开始遍历所有闭合支路最后检查遍历到的节点数是否等于33。这步看似简单但很多初学者会犯一个隐蔽的错误只检查了节点连通性没有检查支路是否形成环。其实辐射状约束包含两个条件连通33个节点都可达和闭合支路数为32。只要闭合支路数不等于32即使节点全部连通也必然是带环的网络不可行。两个条件都满足才是完美的辐射状拓扑。我把这两个检查封装成一个isRadial函数任何粒子在评估前先过这一关。6.3 第三个坑惩罚系数导致算法“学坏”惩罚系数太小不可行解的适应度可能比真正的最优解还好算法会堂而皇之地输出一个电压越限的方案惩罚系数太大又会把整个适应度函数的尺度拉爆粒子对网损差异变得不敏感搜不到精细解。我实测下来电压惩罚系数取1000到5000之间、拓扑不可行惩罚取500左右效果比较好。另外惩罚项最好用平方形式而不是线性形式平方惩罚对小幅越限相对宽容对大幅越限严厉得多这更符合工程实际——电压偏差一点是可以接受的偏差多了是严重的质量问题。6.4 第四个坑和论文结果对不上时别急着怀疑算法复现论文最纠结的时刻就是自己跑出来的最优开关组合和论文里对不上。先别怀疑算法有问题按这个顺序排查先看数据是不是同一套IEEE33节点再看初始网损是不是202.67kW再看论文里的结果是不是多次运行取的最好值而你拿单次运行去比最后再看论文的收敛条件和潮流精度是否和你一致。很多论文为了版面好看会写“运行50次取最优”但正文里从来不提这个细节。碰上这种情况多跑几次自己算法取最优值去比大概率就能对上了。我自己的经验是最初复现时最优值跑到142kW和论文的139.5kW差距明显一度怀疑代码写错了。后来检查发现是我把变异概率设成了0.2变异力度太大粒子很难在最优解附近稳定收敛。调回0.06以后最优值立刻降到139.6kW左右和论文基本一致。这个教训说明改进算法的参数敏感性非常高论文里给了一组参数不代表这组参数在你的实现上也一定最优需要自己做小规模参数扫描。结尾给后续扩展留几个方向以我个人复现这类配电网优化论文的体会来说最忌讳的就是一上来盯着“跑出论文最优值”这个目标那样很容易走火入魔。先把潮流计算写对确认初始网损精确匹配再用标准BPSO跑通整个流程最后再叠加各种改进机制——每加一个改进点都单独对比效果这样才能真正搞清楚哪个改进在起作用、起了多大作用。最后再分享一个小技巧调参阶段把粒子数设小一点比如10个粒子、50代快速验证算法逻辑有没有问题确认没问题后再把粒子数加到40或50跑正式实验。这样一轮调试只需要几分钟而不是每次改动要等十几分钟才能看到结果。这个项目后续如果想继续扩展可以考虑加分布式电源、考虑开关操作次数限制、或者把目标函数从单纯的网损扩展为网损加电压偏差的加权和改进BPSO的框架不用大改就能适配这些变化这也是这个方向经久不衰的原因所在。
返回列表