台风过境之后那几天,是我做配电网抢修恢复课题时最焦虑的一段时间。停电面积大、抢修队伍有限、应急发电车就那么几辆,各条馈线的失电负荷都在抢着要资源,调度员拍脑袋排序的结果往往是“修了不少,但重要负荷没先送电”。那时候我意识到,抢修恢复不能只靠经验,它本质上是一个多约束、多资源协同的优化决策问题,而粒子群算法(PSO)恰好是解决这类问题的成熟工具。这个项目就是围绕“弹性提升”目标,用粒子群算法求解多种应急资源参与配电网抢修恢复的最优调度方案,配套Matlab源码版本15275期。把这一整套思路和实现细节完整拆开讲,适合正在做配电网弹性提升、故障恢复课题的研究生,以及想用智能优化算法解决实际调度问题的工程师参考。
1. 为什么要把“抢修恢复”当成弹性提升问题来做
1.1 从“修得快”到“恢复曲线面积”的认知变化
传统的配电网故障抢修,考核指标就是“复电时间”。谁先修好、几点全复电,调度日报上写清楚就行。但这两年“弹性电网”的概念提得越来越多,大家发现单纯比“修得快”不够——一次强台风扫过去,配网可能有几十处故障点,抢修资源短期内不可能全部覆盖。这时候真正要关心的,不是最终的恢复时刻,而是整个恢复过程中失电负荷的累积损失,也就是故障发生到完全恢复这段时间里,系统到底“扛住了多少、恢复得有多快”。
这里有一个很直观的类比:一个人骨折住院,好的治疗方案不是在床上躺够三个月再出院,而是“尽早手术、尽早下地、尽早恢复功能”。配电网也一样,目标不是“最后全复电”,而是“重要用户在最短时间内恢复供电”,让失电功率-时间曲线下的面积尽量小。这个面积,在弹性研究里常常被翻译成“弹性恢复过程指标”。
所以这个项目的第一个核心设定,就是把目标函数从“最小化恢复时间”改造成“最大化加权恢复效益的累计值”。不同等级的负荷(医院、通信基站、居民区)权重不一样,恢复一个一级重要负荷和一个普通居民负荷,对目标函数的贡献完全不同。这样一来,粒子群算法优化的就不再是一个单纯的时间问题,而是一个带权重的时序调度问题。
1.2 抢修队伍、应急发电车、抢修物资三类资源的协同困境
“多种应急资源”这几个字,听起来简单,真正建模的时候才会发现有多难缠。参与抢修恢复的资源至少分成三类:抢修队伍、应急发电车(含移动储能)、抢修物资。它们之间不是独立的,而是相互牵制的。
抢修队伍负责修复断线、更换变压器等永久性故障,资源属性是“人去干活”,受到移动时间、任务顺序、人员技能类型三个约束。应急发电车负责在故障未修复前给关键负荷临时供电,资源属性是“移动电源”,受到接入点位置、容量上限、接线方式约束。抢修物资则是两者的后勤保障——队伍出去抢修,得有导线、金具、变压器配件;发电车接入,得有合适的电缆和接头。
难点在于,这三类资源的调度决策是耦合的。比如某节点既安排了抢修队伍去修,又安排了应急发电车去临时供电,那么发电车的接入时段就最好避开工频停电窗口;再比如物资都堆在一个仓库里,但两支队伍同时需要同一种型号的导线,分配就必须有先后。把这三类决策拆开单独优化,结果大概率是局部最优;放在一个模型里联合优化,维度又一下子膨胀。
这个项目采用的建模思路是:把三类资源的调度决策统一编码进一个粒子,用粒子群算法进行联合寻优。队伍怎么派、发电车往哪走、物资怎么分,都通过解码函数从粒子位置里还原出来。这样做的好处是全局性好,坏处就是编码设计和维数控制需要特别用心,后面我会详拆。
1.3 目标函数怎么设计才贴近“弹性提升”
先给出我在项目中实际使用的目标函数框架,它分为三个部分整合成一个综合适应度值:
| 目标项 | 表达式含义 | 权重倾向 |
|---|---|---|
| 重要负荷加权恢复量 | 各时段已恢复负荷功率 × 负荷权重 × 时段步长的累加 | 权重最大,体现弹性提升核心 |
| 资源损耗惩罚 | 队伍绕行距离、发电车燃油/电量消耗折算 | 中等权重,避免蛮干式调度 |
| 约束违反惩罚 | 违反容量、时间窗、物资库存约束的惩罚值 | 必要时加大,保证可行解 |
实际Matlab代码里,适应度函数核心行是这么写的:
matlab复制fit = sum(sum(W_load .* P_restored .* mask_restored)) * dt ...
- lambda1 * sum(sum(D_route)) ...
- lambda2 * sum(violation_penalty);
W_load是负荷权重矩阵,P_restored是逐时段恢复功率,mask_restored是恢复状态指示。第一项把“恢复了多少重要负荷”这个弹性核心指标量化了,第二项是资源消耗代价,第三项是约束惩罚。
有一个容易忽略的细节:这个目标函数天然符合“弹性提升”的直觉——它奖励“早恢复”而且“恢复得多”,但不强制每一分钟都动作最少。粒子群在迭代中会自动发现,把发电车优先开到权重最高的医院节点,比先抢修一个偏远分支线路划算得多。这种结果不是人工规则写死的,而是优化算法自己学出来的,这正是智能优化方法在这个场景里最有价值的地方。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 把抢修调度问题建模成粒子群可解的数学问题
2.1 从实际调度需求到决策变量的抽象
要应用粒子群算法,第一步不是写代码,而是把实际调度需求翻译成数学语言。这个项目里,我梳理出了三组决策变量:
第一组是“任务—队伍”分配关系。假设故障点有R个,可用抢修队伍有C支,需要决定每个任务由哪支队伍执行,以及各队伍内部的任务执行顺序。这里我用了“优先权矩阵”的方式,让每个队伍按粒子解出的优先权数值对任务排序,从而绕开复杂的顺序约束。
第二组是“应急发电车—失电节点”分配关系。假设有B个关键失电节点候选接入点,有G辆发电车,需要决定每辆发电车去哪个节点、什么时刻接入、输出多少功率。粒子中对应段直接映射成“接入点编号”,0表示暂不接入。
第三组是“抢修物资”分配关系。假设有K种物资、S个物资囤积仓库,粒子中对应段表示从某个仓库向某个任务点调配的物资数量比例。
把三组变量倒进一个粒子位置向量里,粒子维度就是 R*C + B*G + K*S。以1个35节点配网为例,如果故障任务10个、队伍4支、关键节点6个、发电车2辆、物资类型5种、仓库2个,总维度是 10*4 + 6*2 + 5*2 = 62,这个规模对PSO来说完全可控。
2.2 粒子编码设计:三段式实数编码与解码机制
粒子群算法天然适合连续实数域的优化,但抢修调度偏偏是离散决策为主。这是本问题建模时最大的矛盾点。我最终采用的是“实数编码 + 解码映射”的思路,而不是直接改成二进制粒子群或离散粒子群。
具体编码方式如下:
matlab复制% 粒子位置向量结构 [段1:任务优先权矩阵 | 段2:发电车接入点编号映射 | 段3:物资分配比例]
% 粒子维度 = R*C + B*G + K*S
% 每个维度取值在[0,1]区间连续变化
x = rand(1, R*C + B*G + K*S);
解码分成三段处理:
段1(长度为R*C):读取后重塑为R行C列的优先权矩阵。每支队伍从优先权最高的任务开始依次执行,同时检查移动路径时间是否满足时间窗约束,不满足则顺延任务顺序。
段2(长度为B*G):对每个关键失电节点,把它对应的G维向量归一化后按最大值索引作为接入发电车编号,0-1区间内用一个阈值判断“是否接入”。这一段的处理核心是保证一辆发电车最多接入一个节点。
段3(长度为K*S):重塑为K行S列的分配比例矩阵,按列归一化让各仓库物资分配总和不超过库存。
这种设计的最大好处是,粒子群的所有搜索操作(速度更新、位置更新)都不用改,算法在连续空间里自由飞行,解出来的粒子位置通过解码函数就能变成一套完整的调度方案。坏处是解码过程会引入一部分映射误差,某些相邻区域在编码空间离得近,但在物理调度上可能差别很大。缓解办法是在初始化阶段用启发式规则生成一批好解加入初始种群,后面我会专门讲。
2.3 为什么用粒子群而不是遗传算法或模拟退火
这个项目在选型时,我对比了遗传算法(GA)、粒子群算法(PSO)和模拟退火(SA)。先说结论:三种算法都能做,但PSO在这个问题上的性价比最高。
| 对比维度 | PSO | GA | SA |
|---|---|---|---|
| 收敛速度 | 快,群体信息共享明显 | 慢,选择交叉变异需要多代 | 慢,单点搜索 |
| 参数敏感性 | 中,惯性权重和加速系数需调 | 中高,交叉率变异率需配合 | 低,但退火计划难定 |
| 离散约束处理 | 靠解码,较为灵活 | 交叉变异易破坏约束 | 依靠邻域构造 |
| 代码复杂度 | 低,Matlab实现简单 | 中 | 低 |
为什么PSO在这类“多资源协同调度”里好使?关键在粒子群算法的信息共享机制。粒子之间通过个体极值和全局极值交换信息,相当于所有候选调度方案在快速交流“哪里有好结果”。抢修调度这个问题,解空间里存在大量“看起来不同、实际恢复效果接近”的方案,PSO的收敛特性能够快速锁定到优质区域,再用后期精细搜索打磨出工程上可用的调度方案。
但是必须承认PSO有个致命弱点:容易早熟收敛,陷入局部最优。这在我初版代码里栽过跟头,后面避坑部分会专门讲怎么抑制。
2.4 约束处理:罚函数为主、修复算子为辅
抢修调度问题的约束特别多,我梳理下来至少五类:队伍移动时间约束、任务执行连续性和先后约束、发电车容量约束、物资库存约束、电网拓扑连通性约束。约束处理方式直接决定算法能不能跑到最后还不产生不可行解。
我采用的策略是“修复算子兜底 + 罚函数引导”。具体分三层:
第一层,解码阶段的硬性修复。段2生成的发电车分配如果出现“一辆车同时分配给两个节点”,直接在解码函数里按照靠近全局最优的经验值选择优先级高的节点,另一个节点置为不接入。这样保证粒子位置再乱,解码出来的调度方案在结构上一定是合法的。
第二层,对无法通过解码修复的约束(比如物资数量超过库存、某队伍总任务时间超过最大工作时段),用罚函数把它折算进适应度值。罚函数系数lambda2不是固定的,前期设小一点让粒子敢于探索边界,后期线性增大把粒子拉回可行域。
第三层,对电网连通性约束,采用“简化潮流判断”而非完整的潮流计算。具体做法是维护一个恢复状态向量,解码阶段按恢复顺序计算各节点是否带电,只有父节点带电且线路完好的节点才视为恢复成功,否则视为失电。这样避免了每次迭代都调潮流计算,大幅减少了计算量。
3. Matlab实现的核心细节
3.1 用IEEE 33节点系统搭一个贴近实际的仿真场景
源码里采用的测试场景是IEEE 33节点标准配电系统。这个系统有33个节点、32条支路、1个变电站出口,常用于配电网重构和故障恢复的算例。我在此基础上做了“灾害化改造”,模拟台风过境后的状态:
| 设置项 | 参数值 | 说明 |
|---|---|---|
| 故障支路 | 4条 | 在随机分支设置断线,形成孤岛 |
| 失电节点 | 7个 | 含1个一级重要负荷(医院),2个二级负荷,4个三级负荷 |
| 抢修队伍 | 3支 | 每队初始位于不同节点 |
| 应急发电车 | 2辆 | 容量分别为400kVA和600kVA |
| 负荷权重 | 一级=100,二级=10,三级=1 | 权重差拉开,体现“弹性提升”导向 |
| 调度时段 | 24小时,步长30分钟 | 共48个时段 |
场景搭建的关键是算例数据的组织方式。我建议把所有网络拓扑、故障信息、资源信息都放在结构体数组里统一管理,不要散落在脚本各处:
matlab复制case_info.net = load_case_ieee33();
case_info.fault_branch = [12 17 20 25];
case_info.load_weight = [100 10 10 1 1 1 1];
case_info.crew = struct('pos', {14; 22; 29}, 'speed_kmh', {30; 30; 30});
case_info.mgen = struct('cap_kva', {400; 600}, 'pos_node', {0; 0});
用结构体管理的好处是,后续换场景(比如改成IEEE 69节点或者某实际馈线组)时,只需要替换case_info,粒子群主循环和适应度函数完全不用动。
3.2 粒子群主循环的工程化写法
粒子群的核心更新公式人人都会写,但工程化的主循环里,有几个点容易被忽略。我在源码里的写法是:
matlab复制for t = 1:max_iter
w = w_max - (w_max - w_min) * t / max_iter; % 惯性权重线性递减
for i = 1:n_pop
v(i,:) = w * v(i,:) ...
+ c1 * rand(1,D) .* (pbest(i,:) - x(i,:)) ...
+ c2 * rand(1,D) .* (gbest - x(i,:));
x(i,:) = x(i,:) + v(i,:);
x(i,:) = max(min(x(i,:), x_max), x_min); % 边界约束
fit(i) = calc_fitness(decode(x(i,:)), case_info);
end
% 更新个体极值和全局极值
end
工程化写法和教学版最大的区别,在于三点:
第一,边界处理。教学版经常直接丢弃越界粒子或者用随机重置,但实际项目中,对越界粒子采用“反弹”或者“钳制”策略更有效。我在源码里用的是钳制策略,把越界的决策变量拉回边界值,同时将对应的速度分量强行设为0。这样能防止粒子反复越界造成震荡。
第二,迭代中间结果记录。每一代不仅要记录gbest的适应度,还要把gbest解码后的调度方案存下来。避免最后一代gbest直接解码失败(这种情况概率很低但发生过),中间结果可以用来回溯。
第三,提前终止条件。设置连续20代适应度提升不足0.1%时提前结束迭代,可以节省大量不必要的计算时间。配电网抢修场景下,PSO一般在150代以内就能收敛,设300代上限完全够用。
3.3 初始化与多种群机制:有效抑制早熟收敛
我初版代码直接随机初始化粒子群,跑了十几组算例,结果有三组明显没有恢复出全部一级负荷。排查原因,是初始粒子群中没有一个粒子“意识”到医院节点应该优先接入发电车,整个种群被带偏了。
解决办法有两个,源码里都实现了:
第一个是启发式初始化解。在生成初始种群时,把总粒子的30%用贪心规则生成:按负荷权重从高到低排序,优先给高权重节点分配发电车,剩余任务按距离最短原则分给抢修队。这些规则生成的是“虽然不一定最优,但符合常识”的可行解,把它们加入初始种群后,全局最优的搜索起点立刻变得合理了。
第二个是多种群粒子群(Multi-Swarm PSO)。把整个种群分成4个子群,每个子群独立进化,每10代通过“移民机制”交换部分粒子。这样做的好处是,不同子群可以在不同区域搜索,相互之间通过移民交流信息,避免整个种群一起陷入局部最优。代价是计算量增加了大约10%,但对这个场景完全可接受。
实测下来,加入这两个机制后,20组蒙特卡洛实验的重合最优率从75%提升到了96%以上,早熟的问题基本解决。
4. 实测中的坑与调参经验
4.1 维度灾难:把三类资源全塞进一个粒子会出大问题
最开始建模时,我想直接把所有决策变量塞进一个粒子,粒子维度飙到了140多维。结果算法跑起来特别慢,而且适应度函数表面非常崎岖,粒子群基本找不到好方向。
后面做了两个改进。第一,分层编码:粒子骨架仍然是一个向量,但把速度更新拆成三段分别计算,每段的加速系数独立。实际观察下来,段1(队伍任务分配)要稳健搜索,惯性权重可以稍大;段2(发电车分配)决策更离散,速度上要加一个匹配映射函数;段3(物资比例)相对平滑,按常规PSO跑就行。第二,降维消元:对物资分配部分,当某个仓库距离故障点特别远(超过100km)时,直接把对应分配比例置0,不再作为决策变量搜索。这样把维度从140多降到了70多,收敛速度几乎翻倍。
4.2 罚函数系数不是越大越好
罚函数是约束处理里最常用的手段,但参数调起来很磨人。我一开始把lambda2设得很大,希望粒子尽快进化到完全满足约束的状态。结果发现粒子群在迭代早期就全部涌向可行域边缘,但由于可行域内部的地形还没探索清楚,很多明明可以大幅提升恢复效益的方案被错过了。
后来我把罚函数系数设为迭代次数的线性函数:前期lambda2=0.1,让粒子可以适当违反约束去探索更大范围;后期lambda2=50,把解强行拉回可行域。这个“先松后紧”的策略在多个算例里都稳定有效。但要注意,这个方案只在罚函数带来的目标值变化量与恢复效益处于同一量级时才有用,如果系数跨越好几个数量级,还是会导致搜索效率骤降。
4.3 时间粒度选错了,调度方案根本没法落地
第一版仿真里,我用1小时为一个时段步长,总共48个时段。跑出来的方案显示“第2个时段发电车接入节点5”,看起来没问题,但仔细一算,队伍从当前位置开到节点5要45分钟,再加上接线时间,2小时内根本完成不了接入。这是因为时间粒度太粗,忽略了资源到场的时间延迟。
后来把步长改成15分钟,总时段96个,计算量增加了不少,但调度方案的可执行性大幅提高。具体经验是:时间步长必须小于“最短关键动作时间”的1/2。这个场景里,发电车最短移动接线时间约30分钟,步长取15分钟就合适。如果算例规模太大、96个时段跑不动,可以考虑采用“变步长”策略:前6小时用15分钟步长精细建模,后面用1小时步长,因为后期主要剩低权重负荷恢复,精度要求不高。
4.4 与其他算法的对比实验:PSO赢在哪里、输在哪里
为了判断PSO方案的价值,我在同样的仿真场景下跑了遗传算法和模拟退火做对比,每个算法独立运行20次取统计结果:
| 指标 | PSO | GA | SA |
|---|---|---|---|
| 加权恢复效益均值(相对值) | 100% | 96.2% | 92.8% |
| 最优解标准差 | 1.8% | 3.4% | 5.1% |
| 平均收敛时间(秒) | 48.6 | 121.5 | 87.3 |
| 一级负荷恢复率均值 | 100% | 94.3% | 91.1% |
PSO在这个问题上的优势非常明显,主要是收敛快、解质量稳定。不过我也发现,PSO的不稳定场景集中在“物资分配比例”这个子问题上——因为物资是连续变量,粒子群在连续空间里容易在局部最优附近来回震荡,精细度反而不如GA的交叉操作。所以后来我在源码里加了一个局部搜索强化:对最优解在物资分配段附近做一次小范围网格搜索,大约能再提升1%左右的恢复效益。
4.5 几个容易被忽略的工程细节
这套Matlab源码在反复实验过程中踩过几个小坑,虽然不起眼,但直接影响结果的可信度:
第一,数据初始化一定要用固定的随机种子。不同随机种子下,场景生成器生成的故障位置和负荷分布都不同,算法对比实验必须统一随机种子,否则根本没法对比。
第二,适应度函数的归一化处理。恢复效益项的量级(几百MW·时段)和资源损耗项(几十km)相差一个数量级,如果不归一化,后面的项基本不起作用。源码里对每个目标项都除以对应的最大值基准,让三个项的量级都在1左右,才能在加权时有效体现偏重关系。
第三,保存每一代的最优解编码而不是只存适应度值。我遇到过跑完200代之后,gbest适应度很漂亮,但用gbest位置解码出来的方案却出现“物资分配为负值”的诡异问题。追查发现是边界钳制时把某个维度值强制设成了小于0的值,而解码函数对负值处理不健壮。后来在解码函数最前面加了一行x=abs(x)或压缩到[0,1],彻底规避了这类问题。
第四,测试场景要留一个“非典型故障”算例。我在源码里额外配了一个含微网的38节点改良系统,故障不仅包含断线,还包括一处分布式电源脱网,用来测试算法在“多故障类型叠加”时的表现。结果发现发电车接入不仅要考虑负荷权重,还要考虑分布式电源的孤岛运行能力,这个扩展场景对未来研究很有价值。
这套方案最终跑出来的调度结果,比人工经验派工在加权恢复效益上提升了约8%到12%,最明显的改善就是把应急发电车优先导向一级负荷节点,而不是离得最近的故障点。粒子群算法在这个问题上最大的价值,不是给一个“标准答案”,而是帮调度员把“先救哪里、后救哪里”的复杂权衡在几分钟内量化出来,让每一分钟的停电损失都尽量小。
