基于粒子群算法的微电网日前经济调度与需求响应Matlab实现 说实话看到“基于粒子群算法的计及需求响应的风光储燃柴微电网日前经济调度研究”这种题目我脑子里立刻跳出两个字折腾。但我说的折腾不是贬义而是这个课题本身确实能把人折腾明白。做电力系统的人都知道微电网调度看着就是个优化问题但真要落到Matlab代码里约束条件一多粒子群算法收敛不稳需求响应参数调不好结果不是发散就是经济性差得离谱。这篇博文就拿这个课题当主线把建模、代码、调参、踩坑整个过程捋一遍希望能给正在做类似仿真或毕设的朋友一点参考。这个方向适合谁如果你是电气工程、新能源、控制工程方向的研究生或者刚接触微电网优化调度的工程师那这篇文章就是给你写的。你不一定要有很强的数学背景但最好会一点Matlab基础知道什么是适应度函数能看懂约束条件。放心我会把粒子群算法知识、需求响应建模、日前经济调度模型怎么在Matlab里落地一点点拆开讲清楚。1. 为什么微电网日前经济调度要“计及需求响应”1.1 微电网的电源结构与调度难点风光储燃柴微电网这个名字拆开看就很有意思。风、光是不确定性的主力储能在中间起缓冲燃柴机组我习惯理解为燃气轮机和柴油发电机的组合是可调度的骨干电源。整个微电网既可以孤岛运行也可以并网运行所以调度里还涉及从主网购电、向主网售电的功率交互。这种电源结构带来的第一个问题就是“多能源耦合”。风电、光伏出力白天和晚上差异大负荷高峰又常常和光伏出力高峰错位。储能电池能转移电量但是有SOC上下限、充放电功率限制和寿命成本。燃柴机组能稳住功率平衡但启停成本和燃料成本都不低。更麻烦的是如果不考虑需求响应那负荷曲线就是刚性的调度只能被动去追负荷。可一旦把可削减负荷、可转移负荷放进来调度就多了一个自由度整个优化空间瞬间变大。从数学上看这是一个典型的多变量、多约束、非线性优化问题。常用做法是构建一个24小时的日前调度模型以系统总运行成本最小为目标函数把每个时段各机组的出力、储能充放电功率、购售电功率、需求响应负荷调整量都当作决策变量然后求解。这个模型如果规模小用穷举或线性规划还能对付但一旦机组数增多、时段细化到15分钟变量数就会爆炸这时候启发式算法就派上用场了。1.2 需求响应改变的不只是负荷曲线需求响应是我在这个项目里觉得最有意思的部分。传统调度里负荷是固定输入你只能去调节机组出力来匹配它。但需求响应把一部分负荷变成了“可调资源”。比如工业用户的可中断负荷、商业楼的空调负荷、居民侧的智能家电都可以在电价激励或者合同约束下削减或转移到其他时段。在日前调度模型里需求响应通常被处理成两类可削减负荷某个时段可以直接削减一部分用电量但要支付补偿成本。可转移负荷负荷总量不变但可以把高峰时段的用电量平移到低谷时段存在转移上限和时间窗口约束。计及需求响应之后微电网的净负荷曲线会变得更平缓。高峰时段负荷降低燃柴机组就可以少发一些电储能的放电压力也小低谷时段负荷提升风电或光伏的弃电率可能下降。最后体现在成本上往往是燃料成本减少、弃风弃光减少但需求响应补偿成本增加。到底值不值就是要靠优化模型来回答的问题。我自己在仿真里看过一个很有意思的现象当需求响应补偿价格设置得比较高时算法会倾向于保留更多可调负荷不削减因为省下的燃料成本可能cover不住补偿成本当补偿价格低时算法又会“疯狂”削减负荷甚至把削减量逼近上限。这个阈值不是拍脑袋定的它跟系统边际成本强相关。所以后文我会专门说需求响应成本系数怎么标定这也是新手最容易忽略的点。2. 粒子群算法为什么适合这个调度问题2.1 从混合整数规划到粒子群的取舍刚接触微电网调度的人可能会问这不是优化问题吗为什么不直接用线性规划、混合整数线性规划或者商业求解器确实如果你能把模型完全线性化用Yalmip加Cplex或者Gurobi速度和精度都比粒子群好很多。但问题是实际做研究的时候模型往往没那么“乖”。比如燃柴油机的油耗特性曲线通常是非线性的可能带有二次项甚至分段非线性储能充放电效率也可能是非线性的需求响应模型里还经常出现逻辑变量比如一个时段能不能削减、削减到什么比例涉及0-1变量。这时候如果用混合整数非线性规划去求解收敛性、初始解、求解时间都会成为问题。粒子群算法的优势其实很朴素原理简单不用求梯度对目标函数可导性没要求。编码灵活连续变量和离散变量可以混在一起处理。全局搜索能力不错不容易被局部极值困死。Matlab写起来非常快几十行核心代码就够跑一个原型。当然它的缺点也很明显不保证全局最优、结果具有随机性、约束条件处理需要技巧。所以在这个项目里我并不是完全抛弃数学规划而是把粒子群当作一个“能快速给出可行解并方便扩展场景”的工具。尤其是要跟需求响应参数、风光出力曲线做敏感性分析时粒子群的重跑成本非常低特别适合教学和预研。2.2 粒子群算法核心流程与关键参数粒子群的核心思想用大白话说就是“跟着群体里最好的个体走同时保留自己的历史经验”。每个粒子代表一组决策变量在迭代过程中粒子会根据两个“引力”更新自己的速度和位置个体最优pBest这个粒子自己历史跑到过的最优位置。全局最优gBest整个种群到目前为止找到的最优位置。速度更新公式是v(i1) w * v(i) c1 * r1 * (pBest - x(i)) c2 * r2 * (gBest - x(i))位置更新公式是x(i1) x(i) v(i1)其中w是惯性权重控制粒子对之前速度的保持程度c1和c2是学习因子分别代表“向自己学习”和“向群体学习”的强度r1和r2是[0,1]之间的随机数。在微电网调度这个场景里粒子维度一般就是所有决策变量的总和。比如24个时段每个时段有风电出力、光伏出力如果是可调度式的但通常风光是给定值、柴油机出力、燃气轮机出力、储能充放电功率、购售电功率、可削减负荷量、可转移负荷量等变量数量轻松上三位数。粒子群维度一高初始化和参数设计就显得格外重要。我常用的PSO参数范围如下表参数常见取值范围我的推荐值种群规模50~300120~200最大迭代次数100~1000300~500惯性权重w0.3~1.20.9线性递减到0.4学习因子c11~31.5~2.0学习因子c21~31.5~2.0速度上限vMax决策变量范围的10%~20%视具体变量而定为什么w要从0.9线性递减到0.4因为迭代前期希望粒子大步探索扩大搜索范围后期希望粒子在最优解附近精细搜索提高收敛精度。如果你用固定w要么前期收敛太快陷入局部最优要么后期在最优解附近反复震荡。3. 调度模型的目标函数与约束条件怎么搭3.1 目标函数燃料成本、运维成本、购售电成本和需求响应成本日前经济调度说白了就是让明天的系统运行总成本最低。一个完整的目标函数至少应该包含这几块燃柴机组燃料成本。柴油机和燃气轮机的燃料成本通常用二次函数近似C_fuel sum( a_i * P_i(t)^2 b_i * P_i(t) c_i )其中a_i、b_i、c_i是机组油耗特性系数P_i(t)是机组在t时段的出力。机组运维成本。风电、光伏虽然燃料成本为零但运行维护成本不能忽略。储能也有充放电损耗对应的维护成本。C_om sum( k_wt * P_wt(t) k_pv * P_pv(t) k_bat * (P_ch(t) P_dis(t)) )与主网交互成本。并网运行时从主网购电要花钱向主网售电可以赚钱。这里需要注意购电价和售电价往往不一样时段不同价格也不同。C_grid sum( price_buy(t) * P_buy(t) - price_sell(t) * P_sell(t) )需求响应补偿成本。可削减负荷和可转移负荷都需要给用户补偿这部分成本必须纳入目标函数否则算法会为了省燃料成本而无限削减负荷。C_DR sum( cost_cut(t) * P_cut(t) cost_shift(t) * P_shift(t) )目标函数综合起来就是min C_total C_fuel C_om C_grid C_DR我在代码里通常把目标函数写成一个独立的脚本方便随时替换成本系数。比如下面这段简化的Matlab思路function cost objFun(x, data) % x 是粒子位置包含各时段决策变量 % 先解码得到 P_diesel, P_gas, P_ch, P_dis, P_buy, P_sell, P_cut... P_diesel x(1:24); P_gas x(25:48); % ... cost_fuel sum(data.a1 * P_diesel.^2 data.b1 * P_diesel data.c1 ... data.a2 * P_gas.^2 data.b2 * P_gas data.c2); cost_om sum(data.k_wt * data.P_wt data.k_pv * data.P_pv ... data.k_bat * (P_ch P_dis)); cost_grid sum(data.price_buy .* P_buy - data.price_sell .* P_sell); cost_dr sum(data.cost_cut .* P_cut data.cost_shift .* P_shift); cost cost_fuel cost_om cost_grid cost_dr; end3.2 约束条件功率平衡、机组出力、储能SOC与可调负荷有了目标函数粒子群还要在约束条件围成的可行域里搜索。约束条件写不好粒子群就会到处飞结果完全不能用。我一般会把约束分成四类来处理。第一类是功率平衡约束。这是硬约束中的硬约束每个时段都必须满足P_wt(t) P_pv(t) P_diesel(t) P_gas(t) P_dis(t) P_buy(t) P_load(t) - P_cut(t) - P_shift_in(t) P_shift_out(t) P_ch(t) P_sell(t)注意可转移负荷的处理方式。如果净负荷转移导致本时段负荷增加那就用正项如果负荷减少就用负项。这地方不统一后面约束检查就会乱。第二类是机组出力上下限约束。柴油机和燃气轮机都有最小稳定出力和最大出力限制P_diesel_min P_diesel(t) P_diesel_max P_gas_min P_gas(t) P_gas_max风电和光伏在日前调度中通常按预测值处理不作为决策变量因为它们的可调度性很差。如果你要做“风光弃电”可以额外引入弃风、弃光变量但默认情况下它们按预测出力全额消纳。第三类是储能约束。储能不是想充就充、想放就放的它的SOC有连续性SOC(t1) SOC(t) eta_ch * P_ch(t) * dt / Cap - P_dis(t) * dt / (eta_dis * Cap)同时SOC要保持在允许范围内比如0.2到0.9。充放电功率也有上限而且一般来说不能同时充放电这可以通过约束处理技术或者编码方式来规避。第四类是需求响应约束。可削减负荷量不能超过用户允许的削减上限而且一天内总削减量可能也有配额限制。可转移负荷要满足“转移前后总用电量不变”的约束不然就是凭空创造电能了。在粒子群实现里我建议不要指望算法自动满足所有约束而是要在适应度函数里加惩罚项。对于功率平衡、SOC连续性这类等式约束惩罚系数要取大一点对于不等式约束用越界量构造惩罚项就行。3.3 需求响应建模的两种实现方式我之前看过很多论文需求响应建模花活很多但最常用的其实就是两种。一种是基于价格弹性矩阵。把负荷分解为不同时段对电价的响应某个时段的负荷变化量是所有时段电价变化的线性组合。这种方法适合做电价型需求响应分析但需要弹性系数矩阵数据不好弄。另一种是基于激励的可调度负荷模型。设定微电网运营商可以和用户签订可中断负荷合同每个时段最多能削减一定比例削减之后按固定单价补偿。这个模型更贴近实际调度操作参数少容易编进粒子群代码。所以我在这个项目里主要用第二种。为了防止算法“把负荷全削没了”我建议对可削减负荷总量再加一个约束比如一天内累计削减量不超过总负荷的一定比例比如5%~15%。这个比例可以参考实际需求响应项目的参与率。如果你完全不限制优化结果很可能出现负荷曲线被削到很低看起来成本很漂亮但实际不可行。4. Matlab代码实现与调参实战4.1 代码总框架与数据准备我习惯把Matalb工程拆成下面几个文件main.m主程序负责初始化参数、调用粒子群、输出结果。objFun.m目标函数与约束惩罚函数。pso.m粒子群主循环。data_load.m加载风电、光伏、负荷、电价等曲线数据。plot_result.m绘制各机组出力曲线、SOC曲线、负荷曲线对比图。这样分文件的好处是后面换数据、换机组参数、改目标函数的时候不用大动干戈。数据准备这一步最容易被忽视。有些朋友拿到的风电、光伏、负荷数据都是标幺值或者归一化后的没有注意基准功率结果调度结果一看就离谱。我一般用实际单位功率用kW时间按1小时储能容量用kWh电价用元/kWh。数据长度必须是24维向量对应24小时。典型数据示例% 风电预测出力 (kW) P_wt [120, 110, 100, 90, 80, 70, 100, 140, 180, 220, 250, 260, ... 240, 210, 190, 170, 150, 140, 130, 120, 110, 100, 90, 80]; % 光伏预测出力 (kW)夜间为0 P_pv [0, 0, 0, 0, 0, 0, 20, 80, 150, 220, 280, 310, ... 300, 260, 200, 130, 60, 10, 0, 0, 0, 0, 0, 0]; % 基础负荷 (kW) P_load [200, 190, 180, 170, 160, 170, 200, 260, 310, 340, 360, 370, ... 360, 350, 340, 330, 340, 350, 360, 380, 350, 320, 280, 230];这样的曲线虽然不一定是真实数据但足够用来调试算法。如果你想用真实数据可以考虑把公开的微电网数据集裁剪成24维。需要注意数据量越大粒子群维度越高收敛越慢。4.2 粒子编码、解码和适应度函数编写粒子编码是整个程序的关键。我试过两种方式。第一种是直接编码所有决策变量。比如粒子维度为24×NN代表每时段的决策变量个数。这种方式直观但维度高粒子群搜索困难。在一台普通电脑上跑300代、120个粒子可能要等好几分钟。第二种是只编码“可决策”的那部分也就是燃柴机组出力、储能充放电功率、购售电功率、需求响应调整量。风电和光伏按预测值直接代入不参与编码。这样维度会低一些收敛也更快。我最终采用的是第二种核心解码逻辑是先固定SOC初值根据功率平衡等式反推出一个“余量”然后用这个余量去调整某个变量。但这容易导致约束不满足所以我会在解码后做一个修正步骤确保每个时段功率平衡。适应度函数里除了目标函数值我还要加上约束越界惩罚。比如SOC越界就加一个很大的惩罚项。惩罚系数太小算法会无视约束太大又会让目标函数变得不平滑搜索困难。我的经验是先跑一次不带惩罚的目标函数看看成本量级然后取惩罚系数为目标函数量级的100~1000倍。一个简化的适应度函数框架function fitness objFun(x, data) % 解码 ... % 计算成本 ... % 约束越界 penalty 0; if any(SOC SOC_min) || any(SOC SOC_max) penalty penalty 1e6 * sum(max(0, SOC_min - SOC) max(0, SOC - SOC_max)); end if abs(P_balance) 1e-3 penalty penalty 1e6 * sum(abs(P_balance)); end fitness totalCost penalty; end这里有个坑惩罚项的数值可能远大于正常目标函数导致粒子群在早期只关注惩罚而忽略了成本优化。我的做法是随着迭代次数增加逐渐提高惩罚系数让算法先找可行域再在经济性上优化。4.3 运行结果判读收敛曲线、各机组出力计划算法跑完不能只看一个最终成本数字还要看收敛曲线。我判断收敛的标准是目标函数值在最后50代内基本不再下降最多在小数点后两位内波动。如果曲线还在明显下降说明迭代次数不够要加大maxIter如果曲线早早平了说明种群多样性不足可以调大w或增加种群规模。各机组的出力计划是判断结果是否合理的重要依据。正常情况下柴油机和燃气轮机应该尽量保持较平稳的出力和较低的启停次数储能SOC曲线应该是一条平滑的充放电曲线不会出现频繁剧烈波动。如果SOC曲线像锯齿一样跳那大概率是SOC连续性约束没处理好或者充放电功率变化太快粒子群产生了“抖动解”。需求响应结果也要重点看。可削减负荷一般应该集中出现在电价高峰时段可转移负荷应该从高峰负荷段转移到低谷段。如果算法把可削减负荷安排在凌晨低谷时段那说明需求响应成本参数或者时段约束设置有问题调度结果违背了基本逻辑。我习惯画这样的图一张是基础负荷和考虑需求响应后的净负荷对比一张是各机组出力堆叠面积图一张是储能SOC曲线和充放电功率曲线。这三张图基本能说明调度策略的全部信息。5. 常见问题与避坑经验5.1 粒子初始化与边界处理不当导致结果发散这是我踩过最深的一个坑。如果粒子初始化时完全随机生成很多粒子一开始就严重违背功率平衡或SOC约束适应度函数里全是惩罚项粒子的“好坏”完全由惩罚项决定算法很难找到有意义的搜索方向。我的解决办法是初始化时先生成一个“基准可行解”。怎么生成简单粗暴的做法是让柴油机和燃气轮机按负荷平均值出力储能不充不放购电功率按功率平衡反算。然后在这个基准解上加一个较小的高斯扰动生成初始种群。这样每个粒子都离可行域不远算法一开始就能沿着降低成本的方向搜索。边界处理也很重要。Matlab里直接用max/min截断位置虽然简单但会让很多粒子卡在边界上。更好的做法是引入“边界反弹”或“边界吸收”% 越界后把速度反向避免粒子反复越界 x(i, x(i,:) xMax) 2 * xMax - x(i, x(i,:) xMax); x(i, x(i,:) xMin) 2 * xMin - x(i, x(i,:) xMin); v(i, x(i,:) xMax) -v(i, x(i,:) xMax);效果不保证所有问题都有效但至少比直接截断要好能保留一些粒子多样性。5.2 惯性权重与学习因子调参的实操建议粒子群的参数整定很多教程都在讲“经验公式”但你去试的时候会发现不同问题的敏感度差别很大。在这个微电网调度问题里我发现惯性权重w对储能SOC曲线的影响最明显。w过大粒子容易飞得过远SOC曲线波动剧烈w过小收敛快但很容易陷入局部最优最后柴油机出力不稳定。我最终采用w从0.95到0.4线性递减迭代到中间阶段时一次典型运行结果已经很稳定。c1和c2也不宜等都取2。我的实测是c1取1.8、c2取1.5时收敛速度和最终成本比较均衡。c1太大粒子会过度依赖自身历史经验群体信息利用不足c2太大粒子群容易过早聚集到某个局部解。你可以设定一个“弱调参流程”先固定c1c21.5调节w再固定w微调c1和c2。一次只动一个参数不然出了问题根本不知道是谁引起的。另外速度上限vMax也值得注意。如果vMax太大粒子在迭代后期还在大范围震荡无法精细收敛如果vMax太小粒子搜索范围受限。我一般把每个变量的vMax设定为该变量取值范围的15%左右比如柴油机出力范围是100~1000kW那vMax就设135kW。5.3 需求响应参数设置不当的连锁问题需求响应参数的“雷区”主要有三个。第一是补偿成本设置过低。比如你让可削减负荷的补偿单价只有0.1元/kWh而系统边际发电成本可能要到0.8元/kWh那算法会认为削减负荷“白赚”于是把可削减负荷全部用完净负荷曲线被削得很夸张。这样虽然总成本低了但需求响应行为已经完全失真。我的建议是先算一下系统平均度电成本然后让需求响应补偿单价略低于或等于边际成本避免过度削减。第二是可削减上限设置太高。如果每个时段都允许削减50%负荷优化结果可能让负荷曲线变成近乎平线这其实是“用需求响应把系统重新塑形”但实际工程里用户根本不可能削减这么多。我一般限制在10%~20%。第三是可转移负荷的“电量守恒”约束没做好。我见过不少代码把可转移负荷当成独立变量结果总负荷凭空增加或减少了整个功率平衡模型都失去意义。这个约束必须严格检查我建议在适应度函数里加一个专门惩罚项比如转移前后总负荷变化量惩罚系数设成最大。6. 从日前调度出发还能做哪些扩展6.1 从单目标到多目标粒子群很多人在做完单目标调度之后又会想加一个“碳排放最低”或“可再生能源消纳率最高”的目标。这就是多目标优化问题了。多目标粒子群的大致思路是引入Pareto支配关系维护一个外部档案Archive存放一组互不支配的非劣解。每次迭代更新个体最优和全局最优时不再用一个标量值判断好坏而是看支配关系或者使用拥挤距离选择。Matlab里可以自己写也可以用一些开源工具箱。需要注意多目标问题的决策变量和约束基本不变变的只是适应度函数变成了向量以及选择机制变了。我个人的建议是如果只是做论文验证先跑单目标画出各项成本的占比再在约束里把碳排放设成一个上限做单目标约束优化比直接上多目标要稳定得多。6.2 从离线仿真到滚动优化日前调度本质是“开环”调度预测明天24小时的情况一次求出所有时段的计划。但实际运行中风速、光照、负荷都会有预测误差所以更进一步的思路是采用滚动优化或者模型预测控制。滚动优化的思路很简单只执行下一时段的调度指令等新数据更新后再重新优化未来24小时或者未来若干小时。这样每次都修正预测误差。你可以把粒子群写成一个函数在滚动窗口里反复调用。这部分改起来并不复杂但要注意每次优化的初始SOC必须沿用上一时段的状态否则调度结果无法衔接。我在扩展实验里试过把日前调度改成每4小时滚动一次最终总成本会略微上升但系统运行的安全性和负荷跟踪能力明显提升。如果你后续要写小论文这个方向可以作为一个对比场景。Matlab里还有一个思路是利用并行计算工具箱加速粒子群因为每个粒子的适应度计算是独立的完全可以用parfor替代for。在种群规模200、迭代300次的情况下四核并行大约能提速2到3倍。不过要注意parfor里的随机数序列设置如果没设好每次并行跑的结果可能不一样复现性很差。最后说一个我自己的体会。做这种调度仿真最怕的不是算法复杂而是“模型自嗨”。你在Matlab里跑得再漂亮也要回到现实里问一句这个削减负荷量用户真的愿意接受吗储能SOC这样变化电池寿命撑得住吗这两个问题想清楚了你的模型才有说服力代码也才真正算得上完成。先别急着加更多算法改进把基础模型的约束和参数校准到位再谈扩展。我一直觉得粒子群在这个问题里只是一个工具真正值钱的是你对微电网物理过程的理解以及把这种理解转化成代码约束的能力。