微电网调度这件事,凡是碰过电力系统优化的人都知道,它从来不是“发电成本最低”一句话能交代的。光伏和风电出力波动、柴油机燃料消耗和排放、储能电池寿命损耗、与主网功率交换的分时电价,每个环节都有自己的目标函数,而且这些函数之间天然互相打架。这篇文章想聊的,就是用粒子群算法去处理多目标微电网调度问题,把经济性、环保性、可靠性放进同一个优化框架里找折中解。我前阵子用多目标粒子群算法(MOPSO)做过一个微电网日前调度项目,从建模、编码、算法调试到最终画出Pareto前沿,一路踩了不少坑。这篇文章把完整思路、代码骨架和问题排查经验都梳理出来,给准备碰这块的同行做参考。
1. 微电网调度为什么天然是个多目标问题
很多人一开始会想,调度不就是“该开几台机组、储能什么时候充电放电”吗,把它变成一个单目标优化不就行了?真做起来就会发现,单目标模型只能回答“怎么最省钱”,但一个真正能落地的调度方案还要考虑排了多少碳、对电网的冲击大不大、极端天气下能不能保证供电。多目标不是锦上添花的学术要求,而是微电网运行本身就有多重身份:它是用户侧的供电单元,也是配电网里的可调节资源,同时还得承担一定的环保指标。
1.1 三个目标之间的冲突关系
微电网里的主要优化目标至少有三个。
第一个是经济性,也就是系统总运行成本最低。成本包括柴油发电机的燃料费和运维费、从主网购电的电费、储能充放电循环造成的折算损耗,以及弃风弃光带来的机会成本。光伏和风电的边际发电成本接近零,所以经济性最优的方案往往会尽可能买便宜电、用便宜时段充电,而不是把所有新能源出力都全额消纳。
第二个是环保性,目标函数可以写成系统总碳排放最低。柴油机是直接排放大户,从主网购电也要按电网平均排放因子折算碳排放。环保最优的方案会优先压柴油机出力,尽量用新能源,但这就可能导致某些时段需要储能大功率放电,或者让联络线功率剧烈波动,经济性反而变差。
第三个是可靠性或稳定性,工程上常用的替代指标是功率波动最小、电压偏差最小、负荷缺电率最低等。这个目标要求各时段出力尽量平滑,不要一会儿从主网购电、一会儿猛送出去。但这又和“电价低谷多买电、电价高峰多放电”的经济性策略直接冲突。
这三个目标互相掣肘:成本最低的方案往往碳排放高或者收购波动大;碳排放最低的方案通常要依赖储能,成本上不划算;稳定性最好的方案又可能牺牲经济性。所以不能说哪个目标绝对优先,实际调度需要给出一组“谁也不能完全压制谁”的折中解,再由运行人员根据场景挑一个最合适的。
1.2 每类目标背后都躲着物理约束
除了目标函数之间的冲突,微电网调度还必须满足一系列物理约束,这些约束是算法最后能不能落地的关键。
功率平衡是最基本的要求,任意时刻所有电源出力加储能放电功率再加联络线购电功率,必须等于负荷功率。柴油机、光伏、风电、储能、联络线这几个决策变量之间不是独立的,只要其中一个变了,其他变量必须跟着调整,否则等式约束就打破了。
设备功率约束也很好理解,光伏和风电出力不能超过预测值,柴油机有最小稳定出力和最大额定功率,储能充放电功率有上限,与主网交换的联络线功率也有安全限额。
储能电池还得多关心一个状态量——SOC(荷电状态)。它存在一个递推关系:下一时刻的SOC等于当前SOC减去充放电电量除以电池容量,还要考虑充放电效率。SOC不能超过上下限,否则电池寿命会受到严重影响。这个约束在时间序列上具有记忆性,不是每个时段独立的。
柴油机还有爬坡约束,相邻时段出力变化不能超过一定速率。如果某天早上负荷突然上升,柴油机没法瞬间从低出力跳到满发,必须考虑它的爬坡特性。这些约束叠加在一起,调度问题的可行域变得相当复杂。好在粒子群算法对约束处理比较灵活,后面会详细说怎么做。
1.3 典型调度周期与数据准备
做日前调度,最基本的周期是未来24小时,时间粒度常用1小时,目前有不少项目细化到了15分钟。时间粒度越细,决策变量越多,计算量也越大。1小时粒度的好处是数据和分时电价天然对齐,迭代速度也快,适合先跑通算法框架。
数据准备上,需要四类东西。一是负荷预测曲线,一般来自历史负荷数据的回归或深度学习预测;二是新能源出力预测,光伏看辐照度、风电看风速,工程上通常直接给预测功率序列;三是分时电价或实时电价数据,决定了储能“低充高放”的收益空间;四是设备参数,柴油机的燃料成本系数、排放因子、爬坡速率,储能电池的容量、SOC上下限、充放电效率,联络线的最大交换功率等。
这块我建议提前把数据单位统一。很多项目跑出来的结果怪怪的,最后发现是功率单位混用了,一会儿用kW、一会儿用MW,成本函数里价格单位又用了元/MWh,整个优化结果差了好几个数量级。多目标优化里目标函数归一化本来就脆弱,单位不一致会让Pareto前沿的分布完全乱掉。
2. 粒子群从单目标走到多目标的关键改造
粒子群算法(PSO)最初是为了解决单目标连续优化问题而提出的,拿来直接跑微电网调度,必须先把它改造成多目标版本。这中间不是简单改两个函数,而是要对整个算法的选择机制动刀子。
2.1 单目标粒子群的基本记忆机制
粒子群算法的灵感来自鸟群觅食,一个粒子就是解空间里的一个候选解。每个粒子有位置向量X和速度向量V,位置表示一组决策变量,速度表示下一轮移动的方向和幅度。
粒子在进化过程中会记住两件事:自己历史上找到过的最好位置pbest,以及整个种群目前找到的最好位置gbest。每次迭代时,粒子根据自己的速度惯性、向pbest的认知学习、向gbest的社会学习来更新位置。速度更新公式是:
v(t+1) = w * v(t) + c1 * r1 * (pbest - x(t)) + c2 * r2 * (gbest - x(t))
x(t+1) = x(t) + v(t+1)
这里w是惯性权重,控制粒子保持当前飞行趋势的程度;c1和c2是学习因子,分别控制向个体历史最优和群体最优靠近的强度;r1和r2是[0,1]之间的随机数,给搜索引入随机性。
w很大的时候粒子倾向于到处飞,适合全局探索;w很小的时候粒子倾向于在当前区域精细搜索,适合局部开发。常用的做法是让w从0.9线性递减到0.4,前期找大方向,后期精细收敛。
2.2 Pareto支配与外部档案集
单目标PSO里只有一个gbest,谁的目标函数值最小,谁就是群体最优。但多目标问题里没有唯一最优解,只有一组互不支配的解,这时候需要引入Pareto支配的概念。
假设有两个解X和Y,它们都有多个目标值。如果X在所有目标上都不比Y差,并且至少有一个目标严格优于Y,那么就说X支配Y。反之,如果X在成本上更低但碳排放更高,Y在成本上更高但碳排放更低,那么X和Y互相都不支配,两者都可以保留在候选解集中。最终得到的一组互相都不被支配的解,就是Pareto最优解集,把它们画在目标空间里就得到Pareto前沿。
多目标粒子群算法会在种群之外维护一个外部档案集,专门存放当前找到的非支配解。每轮迭代后,把粒子的新位置和档案集里的解做支配关系判断:新解支配了档案集里的某些解,就替掉它们;新解被档案集里的某个解支配,就丢弃;互不支配,就把新解加进去。
档案集解决了“群体最优是谁”的问题。单目标里gbest是唯一的,多目标里Pareto前沿上所有解都是“最优”的候选者,因此算法的领导者可以每次从档案集里挑选,而不是固定为某个唯一的全局最优。
我最初做多目标改造时,想过直接把所有目标的加权和作为单目标函数,跑多次不同权重来生成Pareto前沿。这个方法在目标函数都较为线性、Pareto前沿是凸集的时候勉强可用,但一旦前沿是非凸的,就有一部分折中解永远扫不出来。MOPSO用档案集加支配关系,本质上是在“同时优化多个目标”而非“把多个目标揉成一个”,前沿完整性会好很多。
2.3 领导者选择与拥挤度距离
档案集建好之后,怎么选leader是个容易影响结果但又经常被忽略的细节。如果每次迭代固定选档案集里编号最小的解当leader,所有粒子都朝那一个方向飞,种群多样性很快会退化,最终前沿会缩成一小块区域。
常见的做法有两种。第一种是简单随机法,每次从档案集里随机选一个解作为leader,实现非常容易,实际效果也还行。第二种是网格法,把目标空间划分成很多网格,统计每个网格里有多少个档案解,然后从“解数量较少”的网格里随机选leader。这样算法会倾向于探索稀疏区域,让最终前沿更均匀地铺满整个边界。
为了控制档案集规模,还需要用到拥挤度距离。拥挤度是衡量档案集里某个解周围“有多少其他解”的指标,先把目标值归一化,再按某个目标排序,计算相邻两个解在这个目标上的距离,把每个目标上的距离加起来得到拥挤度。拥挤度小说明这个解周围已经很密,删掉它对前沿分布影响最小。档案集容量有限时,每次都挤掉拥挤度最小的解,保留尽可能分散的非支配解。
这个思路和NSGA-II里的拥挤度排序很相似,区别在于NSGA-II是每一代都重新排序,MOPSO则是在外部档案集维护过程中动态处理。
3. 微电网调度的MOPSO落地细节
有了算法框架,接下来就是把微电网调度问题塞进这个框架里的过程。这一部分我从决策变量编码、目标函数计算、约束处理一直到主流程逐层拆开讲,给出一套可以直接搬过去改的代码骨架。
3.1 决策变量编码与数据准备
先确定调度模型的决策变量。假设系统里有柴油机、储能、联络线和已知的风光出力,一天分成24个时段。一个最直接的做法是编码柴油机出力P_d(k)、储能功率P_b(k)、联络线功率P_g(k)共3个变量乘以24个时段,得到72维的粒子位置向量。每个粒子就是一个完整的调度计划。
但如果把三个变量都当成独立决策变量,功率平衡约束就必须用罚函数来处理,因为随机初始化出来的粒子几乎不可能天然满足等式约束。我在实际项目中更推荐一种“消元编码法”。既然功率平衡方程是 P_pv(k)+P_wt(k)+P_d(k)+P_b(k)+P_g(k)=P_load(k),光伏和风电是已知量,那么在编码时只把柴油机和储能功率作为决策变量,联络线功率通过功率平衡直接解出来。
P_g(k) = P_load(k) - P_pv(k) - P_wt(k) - P_d(k) - P_b(k)
算出来之后检查联络线功率是否在上下限范围内,越限的话再做约束处理。这种编码方式的优点是让等式约束几乎天然满足,决策变量从72维降到了48维,维数降低对粒子群这种随机搜索算法是很大的帮助。
储能功率的编码要特别注意充放电方向。我习惯用一个变量P_b表示储能出力,正数表示放电,负数表示充电,上下限设为[-Pb_max, Pb_max]。这个约定在目标函数和SOC递推里都要统一,否则很容易把充电算成放电,成本计算结果完全错误。
3.2 适应度计算和约束处理策略
第一个目标函数是系统总运行成本,按24个时段累加:
C_total = Σ( C_fuel(k) + C_om(k) + C_grid(k) + C_bat(k) )
C_fuel是柴油燃料成本,等于柴油机出力乘以燃料成本系数;C_om是运维成本,可以按出力乘比例系数;C_grid是购电或售电费用,从主网购电时为正,向主网售电时为负,分别用购电价和上网电价计算;C_bat是按储能循环寿命折算的损耗成本,通常按放电量乘以一个等效折旧单价。
第二个目标函数是总碳排放:
E_total = Σ( EF_diesel * P_d(k) + EF_grid * P_g_buy(k) ) * Δt
EF_diesel和EF_grid分别是柴油机和电网购电的排放因子。储能本身不直接排碳,但如果储能充电的电是柴油机发的,那么这部分排放已经算在柴油机或购电项里了,放电时不需要再重复计算。风光发电的排放近似取零。
第三个目标我建议不要急着选“停电概率”这种离散指标,因为粒子群的搜索过程对连续函数更友好。工程上常用联络线功率波动平方和,把每相邻时段联络线功率差值的平方累加起来,数值连续、梯度平缓,有利于粒子群在决策空间里平滑搜索。如果微电网允许一定负荷卸载,也可以把“负的供电满意度”作为第三个目标,但需要额外的可靠性计算模块,前期会复杂不少。
约束处理是最影响算法稳定性的环节。我采用“可行性优先”的规则:档案集只接受满足约束的可行解;粒子的pbest更新时,如果新解不可行就直接保留旧解;初始化时尽量在变量可行域内均匀采样。再加上对联络线功率越限的惩罚项,让不可行解虽然可能参与迭代但很难进入档案集。这种处理方式比单独调一个惩罚系数要稳得多,也省去了为罚函数系数反复试错的麻烦。
3.3 算法主流程与关键参数推荐
我把MOPSO的主流程整理成下面的代码骨架。这里省去具体的目标函数实现,只保留算法逻辑,方便你替换成自己的微电网模型。
# 多目标粒子群算法主流程(伪代码风格) pop_size = 100 max_iter = 300 archive_size = 40 w_max, w_min = 0.9, 0.4 c1 = c2 = 1.8 # 初始化粒子群 particles = initialize_population(pop_size, dim=48) for p in particles: p.pbest = p.position.copy() evaluate(p) # 初始化档案集:找出当前种群中的非支配可行解 archive = get_non_dominated(particles) for t in range(max_iter): w = w_max - (w_max - w_min) * t / max_iter for p in particles: # 从档案集中选择leader,可用随机法或网格法 leader = select_leader(archive) # 更新速度与位置 r1, r2 = random(), random() p.velocity = (w * p.velocity + c1 * r1 * (p.pbest - p.position) + c2 * r2 * (leader - p.position)) p.position = p.position + p.velocity # 越界修复 p.position = boundary_repair(p.position) # 评估并更新pbest evaluate(p) if dominates(p, p.pbest) or not dominates(p.pbest, p): p.pbest = p.position.copy() # 合并新解并更新档案集 archive = update_archive(archive + particles, archive_size) # 可选:记录HV指标或前沿分布关于参数,直接给出一套经过验证的推荐值供参考。种群规模一般取80到120,决策变量维数越高越要偏大,我这里48维用100粒子;迭代次数取300到500,迭代太少前沿没收敛,太多对24小时调度来说又没必要;惯性权重从0.9线性降到0.4;学习因子c1和c2都取1.8,经典设置;档案集容量取40左右,太小容易丢失极端解,太大计算量上升但分布未必更好。
速度上限Vmax通常取变量范围的10%到20%。Vmax太大会让粒子飞得太猛,容易跳出可行域;太小则收敛太慢、探索不足。这里我取柴油机和储能出力范围差值的15%,效果还不错。
4. 实操中的坑与排查方法
算法框架跑通不难,难的是结果不合理时不知道怎么改。我把实际项目中反复踩过的几个问题整理出来,这些问题你在复现时大概率也会遇到。
4.1 粒子“早熟”收敛,Pareto前沿缩成一个点
表现是运行多次,最终档案集里的解在目标空间里都挤在一小片区域,Pareto前沿明显没有铺开,甚至在极端情况下所有非支配解就是同一个方案。
这类问题基本都是Leader选择策略和种群多样性不足导致的。粒子群向一个强leader过度集中,很快就把所有粒子“吸”到一起,档案集里虽然保存了解,但种群本身失去了继续探寻新区域的能力。
排查顺序我建议这样:先检查惯性权重起始值是不是太小,低于0.8很容易前期就陷入局部区域;再看leader选择,如果是从档案集固定取第一个解的代码,立刻改成随机选择或网格法;最后可以给算法加点变异操作,每迭代几轮随机挑一个粒子,把它的某一维位置重置到可行域内随机值,模拟遗传算法的突变效果,对跳出局部区域很管用。实际测试中,加了变异算子后前沿完整性明显改善。
4.2 等式约束打破之后怎么办
微电网调度里的功率平衡是强约束,如果采用“把所有决策变量都放进粒子位置再罚函数”的写法,大量粒子的等式约束违反度很高,罚函数系数很难调到合适。系数太小,不可行解混进档案集;系数太大,可行域边界附近的本来很好的解也被误判淘汰。
我强烈建议改成前面提到的“消元编码法”,只编柴油机和储能出力,联络线由平衡方程反解。这样粒子群在迭代过程中虽然会暂时打破功率平衡而让联络线越限,但违反的是联络线上下限,是一个相对容易处理的边界约束,而不是严格的等式约束,整个搜索空间的质量会好很多。
如果在遗留代码里不方便改编码,可以试试动态惩罚系数:约束违反量乘以一个随迭代次数增大的权重,前期允许粒子在不可行区域探索,后期强制收束到可行域。但这个方法对参数敏感度高,不建议作为首选方案。
4.3 时间序列与储能SOC的几个隐蔽问题
储能SOC的递推是最容易出bug的地方。如果直接把SOC当作独立的决策变量编码进粒子,粒子更新可能让SOC序列前后矛盾,比如第5时段SOC是0.6、第6时段变成0.4,但按储能功率递推公式算出来第6时段应该是0.8,这种解物理上根本不成立。
正确做法是SOC不作为决策变量,而是在评估每个粒子时,根据初始SOC和粒子携带的储能充放电功率序列逐时段递推得到SOC(k)。递推过程要区分充电和放电效率,充电时SOC上升要乘充电效率,放电时SOC下降要除以放电效率。如果某个时段的SOC越限,就把越限总量计入约束违反度,再按可行性规则处理。
数据对齐问题也很隐蔽。分时电价有低谷、平段、高峰,如果电价时间点和负荷曲线错位一个小时,储能会按照错误信号充放电,结果经济性差好几个点。还有柴油机爬坡约束的时间单位,Δt到底是1小时还是15分钟,爬坡速率单位是kW/min还是kW/h,这些细节在写约束函数时都要仔细核对,不然得到的调度方案在工程上完全不可执行。
4.4 多目标解的评价与最终折中解选择
算法跑完,得到一堆档案集里的非支配解,最后到底选哪一个?很多第一次接触多目标优化的人会卡在最后一步。
我的做法是先用“模糊隶属度函数”做折中决策。对每个目标,找出所有Pareto解中该目标的最小值和最大值,然后给每个解在每个目标上计算隶属度,数值越大越接近该目标的最优值;再把某个解在所有目标上的隶属度取平均,平均值最大的就是综合折中最好的解。这种方法主观性相对小,也方便运行人员根据不同偏好调整权重。
评价算法本身的性能也很重要。如果拿不到理论最优前沿,可以计算档案集的超体积指标HV,用所有解对参考点围成的超立方体体积来衡量覆盖性和收敛性。HV值越高,说明这套参数下的优化结果越好。我在调参时会把每次迭代的HV记录下来,如果HV在前半段涨得很慢,说明参数选择可能有方向性偏差。
最后再分享一个我的经验
多目标粒子群解决微电网调度,算法本身的难度远比问题建模和约束处理的难度低。粒子群代码网上很多,但换一个系统、换一组数据就能跑出有意义结果,靠的是把物理约束理解透、把编码方式选对、把数据单位对齐。我实际调这个项目时,真正花费时间的不是粒子群公式,而是储能SOC递推逻辑和联络线越限处理这两块。
如果你刚起步,建议先用一个很小的算例,比如两个目标、三个决策变量的规模,把Pareto支配、档案集更新、拥挤度排序这些逻辑用单步调试看清楚,再上完整的24时段微电网模型。很多人在大代码里找bug找得头大,反而失去了对算法本身的感觉。粒子群加多目标是个非常好用的组合,尤其适合微电网调度这种连续变量多、Pareto前沿相对光滑的问题。希望这篇文章能帮你少踩几个我已经踩过的坑。