分布式光伏一路扩容,配电网的电压越限和网损问题也跟着摆上台面。我前两年接手一个配网侧的光伏接入课题,白天光照一强,末端节点电压直接往上冲,调完无功又发现有功损耗压不下去——这时候才真正体会到,单做无功优化或者单做有功调度都只能算“半截子工程”,得把有功-无功协调优化这个事整明白。问题落到算法层,就是用改进多目标粒子群优化算法去解,我在Matlab里把基于小生境粒子群的版本完整实现了一遍,从目标函数建模、潮流计算嵌入到Pareto前沿提取都跑通了。这篇就把整个实现思路和代码细节从头到尾捋一遍,适合正在做配电网优化课题、或者想把手里的标准粒子群代码升级成多目标版本的读者参考。
1. 有功-无功协调优化到底在解决什么问题
1.1 为什么要“协调”:有功和无功在配网里根本不是独立变量
很多新手容易踩一个认知误区:觉得有功调度归调度,无功补偿归补偿,两件事各干各的就行。但配电网和输电网不一样,配网线路的电阻R相比电抗X来说大得多,R/X比值高,这意味着有功功率的流动会显著影响节点电压。你算一下潮流方程就明白了,节点电压幅值的变化量不只跟无功Q相关,有功P同样通过线路阻抗分压参与到电压降落计算里。
举个例子,某条10kV馈线末端接了一台1MW的光伏,中午满发时有功沿线路倒送,电压升高;这时候如果只靠电容器或SVC去“压低”电压,补偿容量很容易顶满,还压不到目标值。反过来,如果把光伏有功出力适当削减一部分,电压马上能降下来,虽然损失了一点发电量,但避免了电压越限,也减少了线路上的无功流动损耗。这才是“协调”的物理本质:在计及P与Q耦合关系的条件下,同时调整有功类和无功类控制变量,找到一组综合最优的调度策略。
1.2 控制变量、目标函数和约束条件的完整建模
基于Matlab做这个问题,第一步是明确变量。我在代码里把配电网的优化变量分成三类:
| 变量类型 | 典型变量 | 性质 |
|---|---|---|
| 有功类 | 分布式电源有功削减量、储能充放电功率 | 连续/离散 |
| 无功类 | 分布式电源无功出力、电容器组投切、SVC/SVG无功输出 | 连续/离散 |
| 结构类 | 有载调压变压器分接头档位、联络开关状态 | 整数离散 |
目标函数我这边取了两到三个相互矛盾的指标,这是多目标优化的前提:
- 系统总有功损耗最小:网损直接反映运行经济性。
- 电压偏差最小:让所有节点电压尽量贴近额定值,提高供电质量。
- 约束条件:潮流等式约束、节点电压上下限、支路功率限值、DG容量限值、电容器/分接头离散档位约束。
其中电压偏差项我用的是归一化的均方偏差,避免量纲差异过大导致某一个目标支配另一个。网损和电压偏差这两个目标在物理上存在明显的矛盾:想降网损,往往需要加大无功补偿,但补偿点选择不当又会引起部分节点电压偏高。所以这个问题天然适合用多目标进化算法去求解,一次性得到一簇Pareto最优解,而不是人为加权成一个单目标。
1.3 为什么多目标粒子群而不是加权单目标
有些做法是把两个目标加权重合成一个指标,然后随便用个单目标优化器跑。这个思路在简单场景能用,但权重系数怎么定是个麻烦事,而且一次只能得到一个解。调度员可能今天想更看重电压质量,明天更看重经济效益,你让他每次改权重重新跑一遍,根本不现实。多目标粒子群算法一次运行可以输出整条Pareto前沿,把所有“不互相支配”的折中方案都摆出来,后续再用人机交互或模糊隶属度从中选一个运行点。这才是工程上可接受的流程。
2. 标准多目标粒子群算法在配网优化里的三个“软肋”
把标准MOPSO直接套到配电网问题上,我在实测里发现几个很具体的问题,这也是后来非要用小生境改进不可的原因。
2.1 早熟收敛:粒子全挤在同一个峰上
标准粒子群依赖个体最优和全局最优的引导,迭代后期粒子多样性急剧下降,几乎所有个体都被全局最优位置“吸”过去。配电网的优化问题搜索空间里极值点很多,早熟收敛的后果就是:算法在某个局部Pareto前沿段附近越陷越深,其余区域的潜在最优解完全没被探索到。我第一版用标准MOPSO跑33节点算例,10次独立重复当中有7次最后的解都落在网损较大、电压偏差较小的区域,另一头的电压优区几乎没有任何解,这就是典型的多样性崩溃。
2.2 Pareto前沿分布极不均匀
即使没完全早熟,标准MOPSO的Pareto前沿也经常出现“两段密、中间空”的现象——靠网损端点密麻麻一堆解,靠电压偏差端点也是密密麻麻,中间过渡带散着零星几个点。原因在于粒子群的速度更新完全没有“排斥”机制,解在目标空间倾向于聚团。对实际调度来说,中段折中解往往才是最有参考价值的,前沿分布不均匀直接导致这部分信息缺失。
2.3 离散变量处理粗糙
配网优化里变压器分接头和电容器组都是离散整数变量。标准粒子群的位置更新公式是连续空间设计的,直接round取整,等于丢失了连续位置与离散约束之间的对应关系。我还试过对离散变量用二进制编码粒子群,虽然能在原理上处理整数问题,但高维情况下搜索效率很低,而且和连续变量混合编码后速度更新参数很难统一调。这些都说明,拿来即用的标准算法在该场景下并不够用。
3. 小生境粒子群的核心改进:让粒子“各据一方”
小生境这个词来源于生态学,说的是同一环境里不同物种占据不同的生态位,避免挤在同一块资源上过度竞争。把这种机制搬进粒子群算法,思路非常直观:通过计算粒子之间的距离,对靠得太近的粒子施加“排斥”,强迫种群在搜索空间的不同区域同时搜索。这样一来,算法既能保持全局探索的广度,又能在每个分区内做局部挖掘,一举缓解早熟和前沿不均匀两个问题。
3.1 小生境半径与共享函数的设置
具体到实现,我给每个粒子计算一个共享适应度值。核心公式如下:
% Matlab示意:计算粒子间距离并施加小生境共享惩罚 D = pdist2(X, X); % X为popSize×dim的种群位置矩阵 sigma = 0.5; % 小生境半径,需按解空间范围调整 sh = max(0, 1 - D / sigma); % 共享函数,距离小于半径才产生作用 niche = sum(sh, 2); % 每个粒子的小生境计数 fitness_shared = fitness ./ niche; % 共享后的适应度这个公式里,小生境半径sigma是关键参数。我这边是按解空间对角线长度的比例来取的,大概取对角线长度的1%~3%,太小起不到排斥作用,太大又会让所有粒子互相排斥,收敛变慢。共享后的适应度用于个体最优和档案更新的比较依据——粒子周围同伴越多,它的共享适应度越低,在竞争非支配解的时候就越吃亏。这样实际上给“稀疏区”的解加了权重,Pareto前沿的均匀性就出来了。
3.2 速度更新公式的改性设计
在小生境基础上,我对粒子群的速度更新做了两处改动:
第一,把全局最优引导改为“邻域最优引导”,也就是每个粒子只参考自己小生境内表现最好的粒子,而不是全局的gbest。这样不同小生境里的粒子沿着各自区域的梯度前进,不会全部朝同一个方向冲。第二,惯性权重w采用随迭代次数非线性递减,并叠加一个由小生境计数调制的扰动项。种群密集区w适当增大、增加探索能力;稀疏区w适当减小、强化局部开发。
% Matlab示意:带小生境扰动的速度更新 for i = 1:popSize r1 = rand(1, dim); r2 = rand(1, dim); % 邻域最优取自小生境内适应度最好的粒子 [~, nbIdx] = max(fitness_shared); v(i,:) = w(i) * v(i,:) + c1 * r1 .* (pbest(i,:) - X(i,:)) ... + c2 * r2 .* (gbest_niche(i,:) - X(i,:)); end这里w(i)不是固定的,我会根据当前粒子的niche计数做自适应调整。实测下来这种方法比单纯线性递减w更能平衡探索与开发。
3.3 外部档案集的维护与裁剪策略
多目标粒子群需要一个外部档案用来存放非支配解,这个档案同时服务于两个目的:一是保存已经找到的Pareto前沿,二是为每个粒子提供更新用的全局最优候选。我在档案维护里同样融入了小生境计数——当档案容量超过上限时,优先删除小生境计数大的解,也就是周围解最密集的那些点,保留稀疏区域的解。这一步相当于在目标空间做了均匀化处理,确保最终前沿覆盖尽可能完整。
档案更新的具体逻辑:
- 把当前迭代所有粒子产生的非支配解并入档案。
- 检查档案中被新解支配的解,删除。
- 若档案数量超过容量限制,按小生境计数从小到大保留。
- 从档案中随机选择一个解作为每个粒子邻域最优的候选来源。
3.4 小生境粒子群算法的完整计算流程
我把整个流程整理成了结构化步骤,方便对照代码阅读:
% 改进小生境多目标粒子群算法主循环(Matlab伪代码风格) for iter = 1:maxIter for i = 1:popSize % 对粒子i对应的配网方案做潮流计算 [loss, voltErr, Umin, viol] = PowerFlow(X(i,:), branch, load, dg); % 更新个体最优pbest if Dominates([loss voltErr], [pbestLoss(i) pbestVoltErr(i)]) pbest(i,:) = X(i,:); end % 计算小生境相关指标 D = pdist2(X, X); sh = max(0, 1 - D / sigma); nicheCount = sum(sh, 2); % 更新粒子速度与位置 v(i,:) = w(i)*v(i,:) + c1*rand.*(pbest(i,:)-X(i,:)) ... + c2*rand.*(gbestArchive(i,:) - X(i,:)); X(i,:) = X(i,:) + v(i,:); % 对离散变量做档位取整与限幅 X(i, discIdx) = round(X(i, discIdx)); X(i, discIdx) = max(minX(discIdx), min(maxX(discIdx), X(i, discIdx))); end % 更新外部档案,按小生境计数裁剪 archive = UpdateArchive(archive, X, [loss voltErr]); archive = TrimArchive(archive, archiveMax, sigma); % 自适应调整w、c1、c2 w = wMax - (wMax - wMin) * iter / maxIter; end这个流程看起来不复杂,但每一个环节都有坑。下一节我把代码实现层面的关键细节展开讲,尤其是潮流计算怎么嵌入迭代、约束怎么处理,这都是决定程序能不能跑出合理结果的地方。
4. 在Matlab里实现:从数据结构到潮流计算嵌入
4.1 决策变量编码的通用做法
配电网优化的决策变量包括连续量和离散量,我建议用单个粒子向量统一编码,前段放连续变量,后段放离散变量,通过索引数组区分。比如:
% 变量编码: % X(1:NdgQ) DG无功出力 % X(NdgQ+1:NdgQ+NdgP) DG有功削减比例 % X(NdgQ+NdgP+1:end) 电容器档位、变压器分接头档位这种编码方式的好处是覆盖了所有控制手段,后续扩展储能或柔性负荷时只需要在向量尾部追加维度就行。实测中,混合编码对算法的要求主要在离散变量的取整与边界处理,我在代码里统一用round加limit的实现,避免越界。
4.2 潮流计算与优化循环的嵌套方式
这是代码性能的瓶颈所在。粒子群每迭代一代,每个粒子对应一组控制变量,都要重新算一次潮流,所以总的潮流计算次数是种群规模乘迭代次数。我这里的做法是直接用前推回代法,针对配电网是辐射状网络这个特征做了简化:
% 前推回代潮流,输入支路数据和节点注入功率 function [U, branchI] = backwardForward(branch, S) % 回代:从末端往首端累加支路电流 % 前推:从首端往末端更新节点电压 end辐射状网络的潮流迭代次数通常少,收敛也快,比牛顿-拉夫逊法更适合嵌入到优化循环里。但要注意,如果网络存在环网,前推回代需要先做解环处理,这个我在代码里没有涉及,只针对标准辐射状33节点算例。
4.3 约束处理:罚函数和修复法的折中
约束处理我建议用“修复为主、惩罚为辅”的组合策略。电压越限是最常见的约束违反,我在每次潮流计算后检查电压,对越限节点先做修复:把该节点附近的DG无功出力或电容器档位调整到工作范围边缘,让电压回到允许区间。如果修复后仍然越限,再在目标函数里加罚项。
% 约束处理示意 Umin = 0.95; Umax = 1.05; if any(U < Umin) || any(U > Umax) % 先尝试修复,增加无功补偿出力 % 修复不了则计算罚项 penal = 1e4 * (sum(max(Umin - U, 0)) + sum(max(U - Umax, 0))); obj = [loss, voltErr] + penal; end为什么要“修复优先”?因为纯罚函数法对罚因子的取值非常敏感,罚因子太小则约束形同虚设,太大则把目标函数形状压得严重畸变,算法容易找不到合适的搜索方向。修复法相当于物理上给定一个可行化操作,搜索方向更明确。实际跑下来的效果,修复法配合小生境的收敛速度和最终前沿质量都明显好于纯罚函数。
4.4 小生境参数对收敛行为的影响
我在调参阶段做过一组对比:
| 小生境半径比例 | 收敛代数 | Pareto前沿均匀度 | 极端解覆盖 |
|---|---|---|---|
| 0.5% | 约120代 | 一般 | 两端覆盖一般 |
| 1.5% | 约160代 | 好 | 两端覆盖好 |
| 3% | 约220代 | 略散 | 两端覆盖较好 |
可以看到半径太小等于没加小生境,太大则排斥过度、收敛显著变慢。我这个算例里取1.5%比例最合适。每个网络拓扑不同,这个参数需要做一次小规模灵敏度扫描再定,不能盲抄。
5. 标准33节点算例的仿真结果与前后对比
5.1 算例设定和参数配置
我用来验证的标准33节点配电网算例,基准电压12.66kV,基准容量10MVA,系统参数是公开的标准测试数据,可以直接从网上找到支路阻抗表和负荷分布表。在原始系统基础上,我在17、21、24三个节点分别接入了分布式光伏,容量设置为1.2MW、0.8MW、1.0MW,作为一组典型配置来测试算法。
算法参数方面,种群规模50,最大迭代次数300,外部档案容量100,小生境半径取1.5%解空间对角线,惯性权重从0.9递减到0.4,学习因子c1和c2均取1.5。独立重复20次取统计结果。
5.2 优化前后的网损和电压指标对比
三种场景的结果如下表:
| 场景 | 系统网损/kW | 电压偏差/归一化 | 最低电压/pu |
|---|---|---|---|
| 未优化 | 约205 | 0.062 | 0.918 |
| 标准多目标粒子群 | 约171 | 0.028 | 0.947 |
| 小生境多目标粒子群 | 约158 | 0.021 | 0.958 |
这个表可以看出来,小生境版本在网损和电压偏差两个目标上都优于标准版本。不过要说明的是,这是Pareto前沿上选取折中解之后的对比,单看某一个指标可能标准版本某个解也能达到,但综合协调性没有改进算法好。
5.3 Pareto前沿分布效果
我在论文和报告里都放了一张Pareto前沿散点图,这里用文字描述现象:标准MOPSO得到的非支配解基本聚集在目标空间的左下角一条窄带里,向前沿两端延伸不足;小生境算法的Pareto前沿从网损低端到电压偏差低端都有覆盖,散布均匀,两个极端之间没有明显空档。最能说明问题的是中段折中区域——标准算法的折中解只有零星两三个,小生境版本则有十几个可选,直接为调度决策提供了更丰富的选择。
5.4 收敛过程的典型现象
小生境粒子群的收敛曲线有一个有趣的特点:前50代左右,它普遍比标准粒子群慢,网损下降速率略低。这是共享排斥在起作用,粒子被“推开”、不能集中朝一个方向冲。但过了100代以后,标准粒子群进入平台期陷入局部,小生境版本还能继续找到更优解。我一开始还担心是参数没调好导致收敛慢,后来看到最终结果才明白,前期的“慢”是多样性的代价,后期换来的全局优势值得这个成本。
6. 实际落地时最容易忽略的几个细节
6.1 从Pareto前沿选一个确定解才是工程常态
算法跑完给出一整条Pareto前沿,但对实际调度来说,操作员只需要一个能执行的方案。我这边用的是模糊隶属度法做决策:把每个目标归一化,求每个解的满意度指标,选综合满意度最大的作为最终调度方案。另外也可以让调度员直接看图选点——这正说明Pareto前沿的均匀分布有多重要,前沿缺一块就等于某些偏好选不到解。
6.2 随机算法必须多次独立运行取统计结论
多目标粒子群是随机算法,每次运行的结果都不完全一样。我强调独立运行至少20次,统计最优值、均值、方差和前沿空间覆盖率,不要根据一次运行结果下结论。这在我之前的课题评审里被重点问到过,如果只跑一次,很难证明算法改进的可靠性。
6.3 计算时间的大头在潮流计算,加速很有必要
33节点系统跑300代、50个粒子,总的潮流计算次数是15000次,单次前推回代几毫秒,整体几分钟就能跑完。但换到几百上千节点的实际馈线,计算量会快速增长。我试过两个加速办法:一是在迭代后期放宽潮流收敛精度,因为粒子还在大范围搜索,高精度的潮流结果意义不大;二是用并行计算把种群中每个粒子的潮流计算分配到多个核,Matlab的parfor直接可用,代码改动很小,提速效果明显。这两个优化加起来,300节点规模的配网计算时间能压到可接受范围。
6.4 对原始数据的预处理是保障结果可信的前提
最后提醒一句:把配电网数据读进Matlab之前一定检查支路阻抗、变压器变比、DG容量这些参数的坐标一致性和量纲一致性。我遇到过同一个算例文档在不同渠道下载的版本里,有的把阻抗单位写成欧姆,有的写成标幺值,直接套用会得到完全错误的潮流结果。凡是涉及潮流和优化的代码,数据校验这一步真不能省。
我在跑通这套代码之后最大的体会是:算法改进的空间往往不在技巧有多花哨,而在于对问题特征的把握。配电网的强耦合、变量混合、约束复杂这几个特点,决定了标准粒子群直接套用肯定会在某个环节掉链子。小生境机制本质上是用“空间竞争”换“多样性”,代价是收敛速度,收益是全局最优的质量。读者如果自己在做类似的多目标优化应用,建议先花时间把目标函数的物理含义想透,再动手改算法,这样每一步改进都会更有底气。后续展开的方向也不少,比如把时间维度加进来做日前-日内滚动优化,或者考虑分布式电源出力的不确定性做鲁棒优化,都是在这套代码框架上可以继续深挖的路径。