多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

粒子群算法求解风-水电联合优化:从建模到Matlab复现

粒子群算法求解风-水电联合优化:从建模到Matlab复现 1. 风-水电联合优化到底在解决什么问题从论文选题说起前几天实验室的师弟拿着一张打印出来的论文页面来找我题目是《基于粒子群优化算法的风-水电联合优化运行分析》来自《太阳能学报》。他问我的第一句话是师兄这文章的模型好像不难但我照着写的粒子群算法就是收敛不到理想结果是不是我代码哪里写错了我花了一周时间从数学模型到Matlab代码把这篇EI论文完整复现了一遍。这个过程中踩了不少坑也重新理解了风-水电联合优化这个问题的深层逻辑。这篇博文就把整个复现过程记录下来从为什么值得复现到数学模型怎么搭再到PSO参数怎么整定、代码怎么组织、结果怎么判断全部摊开讲。先说这篇文章做了一件什么事。风电大规模并网之后最大的麻烦是什么是出力随机性和反调峰特性。白天用电高峰往往风小夜晚用电低谷反而风大这种反着来的出力特性让电网很难办。传统做法是依靠火电机组深度调峰去配合风电但火电爬坡速度慢、调节成本高频繁调整还会增加煤耗和设备损耗。水电就不一样水电机组的响应速度是分钟级的开停机灵活、调节范围宽天然适合做风电的缓冲垫。所谓联合优化运行就是在满足负荷需求和各种物理约束的前提下把风电机组、水电机组的出力计划在调度周期内安排到最优让系统运行成本最低、弃风量最小。在新能源电力系统研究里这是一个很经典也很有代表性的选题。它跨了电力调度、新能源并网、智能优化算法三个方向论文在《太阳能学报》上能发出来说明模型框架和实验设计是经过审稿人检验的值得复现和学习。对于正在做相关方向研究、写论文需要对比算法、或者要向PSO算法实用化方向深入的研究生来说把这篇论文完整啃下来比随便跑一个仿真平台自带例程的收获要大得多。复现这件事很多人有个误区以为复现就是把论文里的公式翻译成Matlab代码跑出差不多一样的图就行。真做起来你会发现论文里省略掉的细节远比写出来的多而恰恰是这些细节决定了算法能不能收敛、结果是否合理。我复现过程中的体会是复现论文的正确目标应该是彻底理解这篇论文的建模逻辑和算法设计思路把论文中缺失的信息用合理的方式补全最终跑出一套结果在趋势、量级上都站得住脚的完整代码。数值是否和原文完全一致反而不是最重要的。2. 从目标函数到约束条件联合优化的数学模型搭建2.1 目标函数系统运行成本最小化这类风-水电联合优化文章目标函数的设定思路通常是一致的就是在一个调度周期内最小化系统总运行成本。我不确定该论文中具体各项成本的权重系数是多少但根据《太阳能学报》同类型论文的建模惯例目标函数一般由三部分组成水电机组的运行维护成本、弃风惩罚成本以及如果系统里还有火电机组参与平衡的话还要加上火电的燃料成本。写成数学形式的话简化的目标函数可以表示成[ F \min \sum_{t1}^{T} \left( C_h(P_h(t)) C_{wind_curtail}(t) C_g(P_g(t)) \right) ]其中 (P_h(t)) 是水电机组在时段 t 的出力(C_h) 是水电运行成本通常和出力呈二次函数关系(C_{wind_curtail}(t)) 是弃风惩罚项用来量化风明明有但没用上的损失(C_g(P_g(t))) 是火电补偿出力的燃料成本。这里有个值得注意的设计细节为什么要把弃风惩罚放进目标函数而不是直接约束弃风量为零因为在某些时段受制于负荷水平、水电调峰能力不足、输电通道受限等因素风电没办法完全消纳强行要求风电全额上网会造成系统无解。所以用惩罚项的方式让优化算法在多弃风和多成本之间自动寻找平衡点这才符合电力系统调度的实际逻辑。水电机组的运行成本建模也需要多说一句。很多人默认水电成本为零因为来水不要钱实际上水电的成本主要体现在设备维护、折旧和水资源利用的综合费用上。论文里通常会设置一个很小但非零的成本系数这样做的目的是让目标函数在数学上保持凸性结构避免出现多解或退化情形。另外如果同时有多个水电机组成本函数还会按机组容量加权。2.2 约束条件功率平衡、机组出力范围与水库水量约束条件是这个模型真正的核心。没有约束的优化问题没有工程意义约束写错了则直接决定优化结果是否可行。我梳理一下这类论文中必须考虑的几类约束这也是你写代码时逐一要实现的检查项。第一是功率平衡约束也是最硬的约束。任意时段内风电机组出力、水电机组出力、火电补偿出力三者之和要等于负荷需求忽略网损的简单化假设下[ P_w(t) P_h(t) P_g(t) P_L(t) ]这个约束在建模层面也会影响决策变量的设计。通常会把水电机组出力和风电机组出力作为决策变量火电出力则作为松弛变量由功率平衡方程自动确定。这样做的好处是决策变量维度可控PSO粒子的搜索空间不会无谓膨胀。第二是机组出力上下限约束。每台机组都有技术出力范围水电机组有最小技术出力和最大出力限制风电机组出力受当前风速下可用功率上限限制[ P_h^{min} \leq P_h(t) \leq P_h^{max} ] [ 0 \leq P_w(t) \leq P_w^{avail}(t) ]其中 (P_w^{avail}(t)) 是时段 t 风电的可用出力由风速和风机功率曲线决定。第三是水电的核心约束——水量平衡约束和水库蓄水量约束。水电站不能只看电还要看水。时段之间的水库蓄水量变化等于来水量减去发电流量简化模型不考虑弃水、蒸发和渗漏[ V(t1) V(t) I(t) - Q_h(t) ]同时库容有上下限发电流量也有上下限[ V^{min} \leq V(t) \leq V^{max} ] [ Q_h^{min} \leq Q_h(t) \leq Q_h^{max} ]这个约束是水电区别于火电最明显的特征。火电你只要盯住出力就行水电必须同时盯住当前出力和累计用水量两件事。优化程序里通常会把库容变化累加成一个状态量逐时段检查是否越界。写代码时这里比较容易出错很多人只检查了单时段的出力边界忘了检查水库蓄水量的时序累积约束结果跑出来的优化方案是发电量很漂亮但水库已经抽干了的状态放到实际调度里根本不可行。第四是爬坡约束针对火电机组和水电机组都需要考虑。机组从一个出力点调整到另一个出力点时受机械特性限制不可能瞬间完成这个约束表达式是[ |P_h(t) - P_h(t-1)| \leq R_h \cdot \Delta t ]风电被认为是可以快速响应调度指令的在控制层面通常不设爬坡约束但部分地区的规定会有弃风速率限制这个要根据具体研究场景确定。2.3 风电不确定性的建模方式还要说一个容易忽略的问题风电出力怎么进模型。论文中通常会把风电出力处理成确定性场景即每个时段给一个固定的可用功率序列。这个序列从哪来一般是两种方式一是直接用历史风速数据的典型日曲线经过风机功率曲线转换成出力第二种是用威布尔分布拟合风速概率密度通过蒙特卡洛抽样生成风速场景再取均值作为确定性输入。从复现角度讲如果论文没有提供具体数据我建议用第一种方式找一份某风电场24小时的实际风速数据转换成功率序列后作为输入。这样做的好处是与论文的场景最贴合——多数发表在《太阳能学报》上的相关论文使用的都是典型日运行数据这样实验可复现性最强也方便和已有文献对比。风机功率曲线的转换公式也很重要。标准风机功率输出一般用分段函数描述切入风速以下出力为零额定风速到切出风速之间满发中间段按风速的三次方关系近似。这一步的换算如果出错后面所有优化结果都是空中楼阁。我见过有人在复现时把风速直接当作功率用结果目标函数量级整个就不对了。3. PSO粒子群算法在联合优化场景中的适配与参数设计3.1 为什么选粒子群做这类问题的求解器风-水电联合优化问题的数学本质是一个高维、非线性、带复杂约束的优化问题。如果调度周期是24小时每个时段有2个决策变量水电出力、风电出力总共就有48维。再加上水库水量约束、爬坡约束带来的时序耦合这个问题的可行域在空间中是一片扭曲的形状传统梯度类算法很难处理——目标函数不光滑、约束和决策变量之间高度耦合。粒子群优化算法PSO之所以在这篇论文中能站住脚核心优势有几个第一PSO不依赖梯度信息。它只需要计算适应度函数值不需要目标函数可导。这在实际工程问题上太重要了因为工程模型的目标函数里往往充斥着罚函数、逻辑判断、数据表插值这类不可导操作。第二PSO的搜索机制适合高维连续空间。这个问题的决策变量都是连续的出力值而PSO粒子的位置和速度本身就是在连续实数空间里定义的天然匹配。第三PSO实现简单、参数少、收敛速度快。相比遗传算法需要设计选择、交叉、变异三种算子PSO只需要更新速度和位置两条公式。对于复现论文来说少一个环节就少一个出错的可能。但是要说清楚PSO不是万能药它容易早熟收敛、陷入局部最优。所以论文的贡献点往往不在算法本身而在如何把粒子群和风-水电联合优化的模型特征结合起来设计编码方式和约束处理策略。复现时你要重点看的就是这个结合点。3.2 参数整定惯性权重、学习因子与种群规模PSO的核心公式是速度和位置更新这应该是每个人都能背下来的[ v_i(t1) w \cdot v_i(t) c_1 \cdot r_1 \cdot (pbest_i - x_i(t)) c_2 \cdot r_2 \cdot (gbest - x_i(t)) ] [ x_i(t1) x_i(t) v_i(t1) ]里面的参数不是随手填的。我在复现过程中试过多组参数最终的合理建议如下表所示参数建议值说明惯性权重 w0.9 线性递减到 0.4前期大权重利于全局探索后期小权重利于局部精化个体学习因子 c12.0引导粒子向自身历史最优靠近社会学习因子 c22.0引导粒子向全局最优靠近种群规模50决策变量48维时50个粒子是性价比比较高的选择最大迭代次数300配合收敛曲线判断早熟收敛可以适当减少惯性权重的线性递减策略是我反复对比后最稳妥的方案前期让粒子大步搜索整个空间避免一开始就扎进局部最优后期让小步精调让结果在最优解附近慢慢稳定下来。有些改进PSO会用自适应惯性权重、随机惯性权重等策略性能在某些测试函数上确实更好但在风-水电联合优化这个场景下线性递减已经足够稳定没必要在这个环节引入太多不确定性。种群规模和迭代次数也不是越大越好。我实测过种群100个、迭代500次的配置结果和50个粒子、300次迭代差异很小但运行时间翻了四倍。考虑到复现阶段需要频繁调参实验配置适中、单次运行时间控制在几十秒内是更合理的选择。3.3 约束处理罚函数法与边界修复策略约束处理是PSO应用到工程问题时最关键的环节没有之一。我在第一次复现时就是栽在约束处理上——目标函数值算出来很好但功率平衡条件差了快10%一看就是不可行解被当成最优解了。最经典的约束处理方式是罚函数法。思路是把约束的违反量以惩罚项的形式加进适应度函数让违反约束的解的适应度值变差粒子群体在进化过程中自然淘汰这些不可行解。以功率平衡约束为例罚函数项可以写成[ Penalty \lambda \cdot \sum_{t1}^{T} |P_w(t) P_h(t) P_g(t) - P_L(t)| ]罚系数 (\lambda) 的取值是个值得仔细琢磨的问题。取太小惩罚力度不够算法会容忍大量约束违反换取目标函数值降低取太大又把目标函数原本的梯度信息完全淹没了粒子分不清哪个解更优收敛速度急剧下降。我的经验是先做一轮目标函数量级估算把惩罚系数的量级设为目标函数量级的 (10^3) 到 (10^5) 倍然后在此基础上做几组对比实验微调。边界约束的处理也一样。直接截断法是最常见的粒子速度或位置越界时直接拉到边界值。但要注意截断后粒子的速度方向可能和边界上的最优移动方向不一致所以更稳妥的做法是把边界越界值按比例弹回空间内部同时对应调整速度方向。这块儿虽然是小细节但对收敛稳定性的影响非常明显。高维约束下还有一种工程上很实用的处理技巧就是把决策变量的编码设计成自动满足约束的形式。比如水电机组的出力上下限约束可以在初始化粒子时就直接把位置限制在 ([P_h^{min}, P_h^{max}]) 区间内速度更新时用边界吸收策略这样出力上限约束永远不可能被违反需要特别关注的只是水量累积约束和爬坡约束这类跨时段约束可行域形状会大大简化。4. Matlab复现的代码架构与各模块实现细节4.1 整体结构与调用关系Matlab是复现电力系统优化论文最顺手的工具矩阵运算天然、绘图方便、调试直观。我的代码结构按照模块化思路来组织每个文件职责单一方便单独调试和修改参数。整个工程的目录结构如下文件名职责main.m主程序入口定义全局参数调用各模块data_loading.m加载风速、负荷、水库来水数据fitness_func.m计算适应度值内含目标函数和罚函数逻辑pso_optimize.mPSO主循环处理粒子更新、边界、最优值记录plot_results.m绘制机组出力曲线、目标收敛曲线、水库蓄量变化这种结构的最大好处是调参数不用翻乱糟糟的主程序改粒子群配置去 pso_optimize.m 里改想换目标函数表达式只动 fitness_func.m 就好。复现论文时你要反复做参数敏感性实验模块化能让你不因为改一处代码而引入新的bug。4.2 PSO主循环的实现PSO主循环的代码是整个程序的心脏。下面这段伪代码是我实际使用的结构简化掉日志输出后的核心逻辑% 参数初始化 wMax 0.9; wMin 0.4; c1 2.0; c2 2.0; nPop 50; maxIter 300; nVar 48; % 24小时x2个决策变量 % 初始化粒子的位置和速度 position repmat(varMin, nPop, 1) rand(nPop, nVar) .* repmat((varMax - varMin), nPop, 1); velocity zeros(nPop, nVar); % 计算初始适应度 for i 1:nPop fitness(i) fitness_func(position(i,:), data); end pbestPos position; pbestFit fitness; [gbestFit, bestIdx] min(pbestFit); gbestPos pbestPos(bestIdx, :); % 主循环 for iter 1:maxIter w wMax - (wMax - wMin) * iter / maxIter; % 惯性权重线性递减 for i 1:nPop % 速度更新 velocity(i,:) w * velocity(i,:) ... c1 * rand(1, nVar) .* (pbestPos(i,:) - position(i,:)) ... c2 * rand(1, nVar) .* (gbestPos - position(i,:)); % 位置更新 边界处理 position(i,:) position(i,:) velocity(i,:); position(i,:) max(position(i,:), varMin); position(i,:) min(position(i,:), varMax); % 适应度计算与个体最优更新 fitness(i) fitness_func(position(i,:), data); if fitness(i) pbestFit(i) pbestFit(i) fitness(i); pbestPos(i,:) position(i,:); end end % 全局最优更新 [bestVal, bestIdx] min(pbestFit); if bestVal gbestFit gbestFit bestVal; gbestPos pbestPos(bestIdx, :); end convHistory(iter) gbestFit; end边界处理这里我用了最简单的直接截断策略在实际运行中表现稳定。如果你追求更好的边界处理效果可以换成反射边界或随机重置策略但相应会增加一些计算量。一个细节值得提随机因子 (r_1, r_2) 在速度更新的每一个维度上都要独立生成不能整个粒子只采样一次。否则会人为引入维度之间的相关性削弱搜索的随机性这在面对48维的高维搜索空间时影响尤其明显。4.3 适应度函数与约束检查的实现适应度函数是PSO优化时调用最频繁的函数它的计算效率直接决定整个程序的运行速度。我把适应度计算分为两部分目标函数值计算和罚函数计算。function f fitness_func(x, data) % 解包决策变量 P_h x(1:24); % 24小时水电机组出力 P_w x(25:48); % 24小时风电机组出力 % 目标函数部分 cost_h sum(C_h * P_h.^2); % 水电成本 cost_curtail sum(lambda_curtail * (data.P_w_avail - P_w)); % 弃风惩罚 % 功率平衡火电作为松弛变量 P_g data.P_load - P_h - P_w; if any(P_g 0) cost_g 1e8; % 火电出力为负说明水电风电超出负荷需要弃风处理 else cost_g sum(C_g * P_g.^2); end % 约束违反量计算 penalty 0; % 1) 水库水量平衡约束和库容约束 V(1) data.V_init data.I(1) - P_h(1) / data.eta_h; if V(1) data.V_min, penalty penalty abs(V(1) - data.V_min); end % ... 同理处理后续时段、爬坡约束等 % 罚函数法合成 f cost_h cost_curtail cost_g lambda_pen * penalty; end注意功率平衡约束的处理方式我没有在罚函数里加功率平衡的惩罚项因为通过把火电设为松弛变量功率平衡天然满足。但由此引出一个新问题——如果水电加风电的出力超过了负荷火电出力就变负了这在物理上没有意义所以需要加上一行判断把这种情况设成很大的不利值或者加入弃风处理逻辑。水库水量约束的检查需要逐时段累积计算库容这个逻辑写在循环里。同样地爬坡约束需要比较相邻时段出力差与最大爬坡速率的关系违反时累加惩罚项。4.4 结果可视化与收敛性判断跑完PSO之后代码还有一半的功夫在结果后处理和可视化上。一个完整的调度结果至少需要画四类图第一是机组出力曲线图横轴是24小时纵轴是功率风电、水电、火电、负荷四条曲线画在一起直观显示各时段的功率平衡关系和各电源的分工。第二是水库蓄水量变化图检查24小时内的库容变化是否在所有时段的上下限范围内这张图是验证约束是否真正满足的直观证明。第三是目标函数收敛曲线图横轴是迭代次数纵轴是全局最优适应度值这张图直接反映PSO收敛速度和质量。第四张图我建议额外画出来弃风功率曲线。复现论文时很多人只关注成本忽略了弃风本身就是联合优化要解决的重要目标。把弃风率的数值打印出来和原文给出的结果做一个对比比单纯对比目标函数值更有说服力。figure; subplot(2,2,1); plot(1:24, P_h, b-o, 1:24, P_w, g-s, 1:24, P_g, r-d, 1:24, data.P_load, k--); legend(水电出力,风电出力,火电出力,负荷); xlabel(时段/h); ylabel(功率/MW); subplot(2,2,2); plot(1:24, V, b-o); xlabel(时段/h); ylabel(库容/万m^3); subplot(2,2,3); semilogy(1:maxIter, convHistory, b-); xlabel(迭代次数); ylabel(最优适应度值); subplot(2,2,4); windCurtail max(0, data.P_w_avail - P_w); bar(1:24, windCurtail); xlabel(时段/h); ylabel(弃风功率/MW);4.5 关于收敛性判断的一个经验判断PSO是否收敛不要只盯着最终的目标函数值。看收敛曲线是否有明显的平台期才是关键——如果曲线在最后100次迭代基本是水平直线说明已经收敛如果还在持续下降说明迭代次数不够要增加迭代或者调整参数。另外一个容易被忽视的点是多运行几次看稳定性。PSO是随机算法每次运行的初始粒子位置不同结果会有一定波动。连续运行10次如果每次目标函数值的标准差在可接受范围内我一般控制在1%以内说明算法稳定性可以接受如果标准差很大那大概率是陷入了不同的局部最优需要增大种群规模或重新设计惯性权重参数。5. 复现过程中踩过的坑与排除经验5.1 惯性权重衰减策略的坑我第一次跑PSO的时候惯性权重用的是固定值0.5结果连续运行多次目标函数值始终在某个值附近波动怎么调学习因子都没用。后来把收敛曲线打出来才发现算法在迭代50次左右就停滞了明显是一头扎进了局部最优。换成了线性递减策略之后前期迭代的搜索空间大了很多最终目标函数值比固定权重时好了接近8%。这个改动是复现过程中收益最大的一次参数调整。但线性递减也有坑递减速度太快可能在算法充分探索之前就已经收缩到局部范围递减太慢后期粒子在最优解附近来回震荡难以精调。如果系统的目标函数地形特别复杂可以考虑改用带噪声的自适应权重策略但一般情况下线性递减从0.9到0.4在300次迭代内完成是一个稳妥的起点。5.2 罚函数系数的量级陷阱罚函数系数的调整是另一个让我折腾了很久的地方。我的第一次实验把所有物理量的单位统一成标准单位之后目标函数的量级在 (10^4) 级别我却把罚函数系数设成了10结果功率平衡的违反量完全被容忍了最优解的各项成本都很低但一看功率平衡差得离谱。后来又贪心了一把把罚系数设成 (10^8)结果算法行为看起来正常了但实际目标函数梯度被惩罚项完全淹没收敛极慢。最终的经验做法是将目标函数的各个分量做归一化处理让水电成本、弃风惩罚、火电成本的量级都落在1到100之间然后再把罚函数系数设为 (10^4) 量级。归一化处理不仅让罚系数好调还让收敛曲线更容易解读一举两得。另外要注意罚函数的形式。线性罚函数 (|g(x)|) 在违反量小的时候惩罚也小收敛时会倾向于让违反量极小化但不一定归零。平方罚函数 ((g(x))^2) 则会让违反量极小时惩罚迅速趋近于零收敛后的解更接近严格可行。我在复现时用的是线性与平方混合的策略功率平衡这类硬约束用平方罚库容约束这种允许有少量缓冲的约束用线性罚。这样既保证了硬约束严格满足又不会让软约束过于刚性导致可行域太小。5.3 优化结果与论文不一致的原因分析复现结果的数值和论文不一致这几乎是必然的不需要焦虑。我把我的结果和论文中给出的数据对比过后发现主要有这么几个原因第一数据源不同。论文使用的风速、负荷、来水数据大概率不是公开数据集我们复现时只能用近似数据或自行构造的数据。数据不同最优解自然不同。第二模型简化程度不同。论文里可能考虑了一些我没有建模的辅助约束比如线路传输容量限制、机组启停状态变量等这些都会影响最优解的形态。第三算法参数和随机性差异。PSO本身是随机算法即使完全相同的参数设置两次运行的结果也可能有细微差别。论文中给出的结果通常是多次运行后的最优解而不是任意一次运行的结果。所以复现成功的判定标准我建议调整为四点调度结果中功率平衡约束严格满足所有机组出力都在物理极限范围内水库库容曲线在上下限之间平滑变化目标函数值的量级和论文在同一数量级。只要这四件事都做到了你的复现就是成功的数值上差几个百分点完全可以接受。5.4 从单时段模型扩展到24小时模型的注意点很多刚接触这类问题的同学会犯一个错误先跑一个单时段的优化模型试试水发现结果漂亮得很就把维度直接扩展到24小时。然后发现算法不收敛了、约束一团乱麻。从1小时扩到24小时本质上是把优化问题从2维提升到了48维。高维搜索空间中粒子群的收敛难度是指数级上升的。这时候有三件事必须做一是把种群规模和迭代次数适当提高给粒子更多的搜索机会二是在初始化粒子时尽量利用历史经验的先验知识——比如用上一轮迭代得到的最优解作为本轮初始粒子之一这会大幅加速收敛三是约束检查逻辑一定要写对尤其是跨时段的水库水量累积约束必须逐时段累计验证而不是只看单一时段。我在扩展维度时还遇到过一个隐蔽的问题决策变量中水电出力P_h和风电出力P_w的搜索范围差异很大。水电出力范围可能是20到200MW风电可能是0到150MW。如果直接在这个原始区间里初始化粒子搜索空间的形状会非常不对称PSO收敛很慢。把两个维度的变量都归一化到[0,1]区间在优化结束后再反变换回实际物理值能显著改善收敛性能。这个技巧在有多组不同量级决策变量的工程优化问题里非常实用。6. 最后再说两句复现与应用的心得复现EI论文和读论文是完全两回事。读论文是你跟着作者的思路走复现论文是你自己把路再走一遍还得保证路上每个坑都自己踩一遍、填一遍。一周下来我对风-水电联合优化这个问题的理解比之前看十篇相关综述还要深。粒子群优化算法也不再是教科书上那两条公式而是一整套包含约束处理、参数策略、收敛判断在内的系统工程。如果你也想复现这篇论文我的建议是从模型到算法再到代码逐步推进先搞清楚目标函数和约束条件的每一项是什么物理含义再想清楚PSO的决策变量应该怎么编码最后才是写Matlab代码。代码写完先跑一次完整的24小时调度把四张图都画出来逐张检查里面数据的合理性这样复现一遍之后你对PSO和电力系统联合优化的理解会上一个很大的台阶。
返回列表