改进粒子群算法求解光储系统规划运行综合优化:模型、Python实现与复现要点

发布时间:2026/10/6 22:58:07
改进粒子群算法求解光储系统规划运行综合优化:模型、Python实现与复现要点
搞这个方向的人应该都有过类似的体验看到一篇EI论文摘要里写着“提出了一种改进粒子群算法求解建筑集成光储系统规划运行综合优化模型”理论上思路很清晰可真到自己动手用Python复现的时候光是把这个模型从论文公式变成能跑通的代码就够折腾好几天。我这次复现的就是这类模型——基于改进粒子群算法的建筑集成光储系统规划运行综合优化目标是在同一个框架里同时回答两个问题光伏和储能到底该配多大容量规划层以及配好之后储能每天怎么充放电、建筑跟电网怎么交互运行层。这篇文章会把完整的复现过程拆开讲先分析模型到底在优化什么再把目标函数和约束条件翻译成代码逻辑然后说清楚标准粒子群算法PSO为什么不够用、改进在哪里最后给出可运行的Python实现要点和几个复现时最容易踩的坑。适合正在做光储优化、微电网调度方向论文复现的读者也适合工程师想快速验证一个新算法方案在真实场景下是否可行。1. 这个题到底在优化什么规划决策与运行策略的双层耦合1.1 规划层在决定什么建筑集成光储系统通俗讲就是在建筑屋顶装光伏板、在配电房或者地下室装储能电池让建筑既可以自己发电又可以存电还能跟电网买卖电。规划层的决策变量比较直白光伏的装机容量kW、储能电池的额定容量kWh、储能变流器的额定功率kW。有时候还包含逆变器选型、变压器扩容与否这类0-1变量。这些变量一旦定下来后续十几年的收益上限基本就被锁死了。光伏装少了白天发的电不够用储能也没多少电量可存储能容量配大了初始投资高得吓人一年到头可能就闲置在那里。1.2 运行层在决定什么运行层解决的是“怎么用”的问题。在一个典型日里光伏出力曲线是一个正弦波样子的序列负荷曲线是建筑里空调、照明、办公设备叠加出来的另一个序列储能则需要在每个时段决定充电还是放电、充多少放多少。同时建筑还要决定跟电网买电还是卖电以及各自多少功率。所以运行层的决策变量是全天每个时段通常按1小时或15分钟划分的储能充放电功率、购电功率、售电功率。如果按4个典型日代表全年四季那么运行变量的维度就是4典型日× 24小时× 若干个动作变量直接撑起了优化问题里最大的变量部分。1.3 为什么“规划运行”必须一起算这俩阶段听着像是可以分开做的先粗算一下容量需求再在固定容量下做运行调度。但这样做的结果往往是偏保守甚至互相矛盾。举个例子如果我一开始把储能容量设得过大运行调度时会发现最优策略是每天最多用掉一小半电量因为电价差不足以覆盖深度充放电带来的效率损耗。反过来如果运行策略只追求最大化自消纳率那就会倾向于把储能耗尽长期满充满放会加速电池衰减运行维护成本上升又反过来影响规划段的经济性评估。这就是典型的强耦合问题必须在一个统一的优化模型里同时找容量和运行策略的“最优组合”。2. 目标函数与约束条件怎么转成代码能算的数学模型2.1 目标函数经济性指标怎么取舍复现这类论文时最常见的目标函数是年综合费用最小化。它由四部分组成初始投资的年化值、年运行维护费用、年购电费用、减去年售电收入。初始投资要用资金回收系数CRF折算成年值公式是CRF r × (1r)^L / ((1r)^L - 1)其中r是贴现率L是设备寿命。光伏和储能寿命不一样光伏通常按25年、储能按10年左右计所以折算的时候得分开算。这一步很多人会漏直接拿总投资除以寿命年限误差会很大。用公式表达就是min C_total CRF_pv × C_pv × P_pv CRF_bat × C_bat × E_bat C_power × P_pcs C_om Σ_t (price_buy(t) × P_buy(t) × Δt) − Σ_t (price_sell(t) × P_sell(t) × Δt)运行维护费一般按初始投资的百分比比如1%~2%来估算购电分时电价和售电电价则是两个不同的价格序列。2.2 等式约束与不等式约束的矩阵化压缩模型里约束条件不少但归纳起来就几类我在代码里全部用numpy矩阵和向量处理避免写for循环串行判断导致速度太慢。第一类是功率平衡约束每个时段都要满足P_pv(t) P_dis(t) P_buy(t) P_load(t) P_ch(t) P_sell(t)四个变量里有两个自由度代码里最好另外两个变量表达P_buy(t) P_load(t) P_ch(t) P_sell(t) − P_pv(t) − P_dis(t)这样平衡约束就被自动消去了优化的搜索空间能缩小一圈。第二类是储能系统约束包括SOC递推公式SOC(t1) SOC(t) η_ch × P_ch(t) × Δt / E_bat − P_dis(t) × Δt / (η_dis × E_bat)以及SOC上下限0.1~0.9、充放电功率上下限不超过P_pcs、同一时段不能同时充放电。同一时段不能同时充放电这种约束简化处理可以用一个很小的正数罚项严谨处理则引入0-1变量但那样会变成混合整数问题粒子群就得配合修复策略来解。第三类是电网交互约束购电功率上限和售电功率上限通常设为同一个值比如200kW。第四类是决策变量边界约束光伏装机容量不超过屋顶可安装面积折算的上限储能容量有离散档位的话就做就近取整处理。2.3 典型参数的取值参考复现时我用了下面这组参数基本符合多数EI论文的量级跑出来的结果趋势上是合理的参数取值说明光伏单位造价4500元/kW含安装储能单位容量造价1800元/kWh磷酸铁锂PCS单位功率造价1200元/kW双向变流器贴现率5%年化折算用光伏寿命25年CRF折算储能寿命10年CRF折算分时购电电价峰1.2/平0.75/谷0.38元/kWh三费率售电电价0.65元/kWh固定采样间隔1小时24个时段/天典型日数量4个按季度聚类这些参数不需要跟原论文完全一致因为原始数据拿不到但量级一定要对否则结果很容易出现“储能配了0容量”这种退化结果——那往往不是算法问题而是分时电价差不够覆盖储能投资成本。3. 标准PSO为什么不够用改进粒子群的三个关键改动3.1 惯性权重与学习因子的动态调整标准粒子群的核心公式大家都知道v w × v c1 × r1 × (pbest − x) c2 × r2 × (gbest − x) x x v但标准PSO在光储规划这个问题上有两个先天不足一是搜索后期粒子速度衰减很快容易还没找到好位置就全体聚集在局部最优附近二是运行变量维度动辄上百维标准PSO几乎不具备全局搜索能力。所以论文里的“改进”通常第一步就是惯性权重非线性递减。线性递减虽然简单但前期全局搜索时间不够后期局部收敛力度也不够。我自己试下来效果最好的是幂函数递减w (w_max − w_min) × ((k_max − k) / k_max)^alpha w_minw_max取0.9w_min取0.4alpha取1.5。迭代前40%期间w始终维持在0.7以上保证粒子有足够的速度去探索不同容量组合到后期w快速跌向0.4让粒子群体集中打磨最优区域。学习因子也做了互补变化。c1表示向个体历史最优学习的程度c2表示向全局最优学习的程度。前期c1应该大一点让粒子自己做主后期c2应该大起来让粒子更信任群体经验。实测c1从2.5线性降到0.5、c2从0.5升到2.5的效果比固定双因子好很多。3.2 约束处理方式罚函数还是修复策略改进PSO与标准PSO最大的区别往往藏在约束处理里。规划运行优化模型中的等式约束和不等式约束非常多如果不处理就直接用粒子群去搜大概率在前几十次迭代里几乎找不到一个可行解。罚函数法是最通用的方案把每个违反约束的量求平方后乘以一个大罚因子加进目标函数里。这样做的好处是简单不用改动粒子群的结构缺点是罚因子太小约束会放松罚因子太大会形成“硬墙”粒子飞不进去收敛曲线会非常难看。我自己复现时采用的是“动态罚函数储能SOC修复”的混合策略。具体来说SOC越界不直接罚而是把SOC序列重新缩放到[0.1, 0.9]区间再做功率平衡修正。这个操作能大幅提高粒子群搜索效率。功率平衡通过2.2节的变量替换自动满足。充放电功率越限压缩到上下限边界。对仍然无法恢复的约束再计入罚项。这个混合策略的思路是能用物理逻辑修复的就先修复修复不了的才交给算法去罚这样粒子群不会把大量算力浪费在寻找可行域入口上。3.3 混合离散-连续变量的特殊编码规划变量里的储能容量在实际采购时是有离散档位的比如50kWh一档但光伏容量和功率变量是连续的。如果全部按连续变量搜索最后取整会产生误差如果全部按离散变量处理维度和搜索复杂度会爆炸。我采用的方式是把粒子位置向量拆成三段来编码前2维是光伏容量和储能容量连续值第三维是PCS功率连续值后面的维度是各个典型日每个时段的充放电功率和购售电功率连续值。每次迭代结束以后只对储能容量做取整到最近档位的操作其他变量保持连续。这样既保持了粒子群的连续搜索特性又保证了最终给出来的方案是工程上能落地采购的离散容量。4. Python实现的分模块拆解从数据处理到迭代求解4.1 数据准备与典型日场景提取原始数据通常是8760小时的建筑负荷序列和光伏出力理论值序列。复现时我没有直接拿全年8760小时去做优化那样不仅计算量爆炸而且相邻时段的高度相关性会让粒子群陷入维度灾难。先用KMeans做典型日聚类。把每天24个负荷值24个光伏出力值拼成48维的特征向量然后用sklearn.cluster.KMeans聚成4类代表春夏秋冬四种典型日。聚类完成后每类的样本数量占比就是该典型日在全年优化中的权重。需要注意KMeans对初始聚类中心敏感我固定了random_state42并且跑了多次验证聚类结果的稳定性。from sklearn.cluster import KMeans import numpy as np # X_daily shape: (365, 48)前24维是负荷后24维是光伏 kmeans KMeans(n_clusters4, random_state42, n_init20).fit(X_daily) labels kmeans.labels_ weights np.bincount(labels, minlength4) / 365.0 typical_days np.array([X_daily[kmeans.labels_ i].mean(axis0) for i in range(4)])聚类完成之后4个典型日的24小时负荷序列和24小时光伏出力序列就是后续运行优化的基础输入。权重weights则用来把4个典型日的运行费用折算成年费用这样目标函数才能跟“全年”的经济账对得上。4.2 粒子编码与种群初始化粒子编码是整个代码最核心的数据结构。我定义每个粒子为一个一维numpy数组长度由三部分组成3规划变量 4典型日× 24时段× 3储能充电功率、放电功率、购电功率。实际算下来长度是3 4×24×3 291维每个粒子291维种群规模设40个这个维度下粒子群勉强还能跑但已经能明显感到收敛速度变慢。初始化的时候不能纯随机不然大量粒子初始解严重违反功率平衡罚函数值会大到把所有粒子都拖在不可行域边缘。我的做法是先根据当天光伏出力和负荷曲线用启发式规则生成一个粗糙的初始运行策略光伏不足且电价高就放电光伏富余且电价低就充电。把这个粗糙解作为其中一个粒子的初始位置其余粒子在这个解周围加随机扰动生成。这样做的好处是种群一开始就有几个粒子落在可行域附近收敛速度明显加快。4.3 迭代主循环与多目标处理主循环就是一个典型的PSO迭代框架但每次迭代里要依次完成计算所有粒子的目标函数与罚项、更新个体最优pbest、更新全局最优gbest、按动态参数的公式更新速度和位置、对位置做边界处理和储能SOC修复。多目标处理方面如果原论文是同时优化经济性和碳排放我复现时用的策略是加权和法把碳排放量乘以一个碳价因子折算成费用加进目标函数。这样粒子群只需要维护单目标的排序关系实现简单而且改动目标函数不影响算法主体逻辑。追求帕累托前沿的话需要引入非支配排序代码量会增加不少不利于快速复现验证。核心迭代代码大致是这样的结构for k in range(max_iter): w (w_max - w_min) * ((max_iter - k) / max_iter) ** 1.5 w_min c1 2.5 - 2.0 * k / max_iter c2 0.5 2.0 * k / max_iter for i in range(pop_size): # 计算目标值含罚函数 cost[i] objective_function(positions[i], typical_days, weights) # 更新个体最优 if cost[i] pbest_cost[i]: pbest_cost[i] cost[i] pbest_pos[i] positions[i].copy() # 更新全局最优 if cost[i] gbest_cost: gbest_cost cost[i] gbest_pos positions[i].copy() for i in range(pop_size): r1, r2 np.random.rand(2) velocities[i] w * velocities[i] c1 * r1 * (pbest_pos[i] - positions[i]) \ c2 * r2 * (gbest_pos - positions[i]) positions[i] velocities[i] positions[i] repair_and_bounds(positions[i], typical_days)每一轮迭代结束后把gbest_cost记录到列表里后期直接画收敛曲线。建议每一轮都打印一次当前最优配置和最优费用方便肉眼判断是否卡在局部最优。4.4 结果可视化与敏感性分析跑完之后要输出的结果至少有三样一是收敛曲线这是证明改进算法有效性的第一手证据二是最优规划结果表包含光伏容量、储能容量、PCS功率和年化总费用三是典型日的储能充放电曲线和购售电曲线用来检查运行策略是否符合直觉比如应该在光伏富余时充电、在傍晚电价峰值时放电。我习惯额外加一个敏感性分析脚本把光伏单位造价从4500元/kWh降到2000元/kWh看最优配置里光伏容量会不会上升储能容量会不会因此改变。这种分析写论文时非常好用能直观体现模型的工程指导意义而且实现成本也就一个for循环的事。5. 复现EI论文时最容易翻车的几个点5.1 典型日聚类的坑最容易翻车的环节其实是数据预处理而不是算法本身。KMeans聚类之前如果负荷和光伏出力没有做归一化光伏出力的数值范围0到几百kW会完全压过负荷曲线的形状特征几百到上千kW的变化聚出来的“典型日”基本只看负荷大小光伏特征全被淹没了。解决办法是先把每个特征维度做标准化再聚类聚类完成后再把典型日的数值还原回原始物理量纲。另外聚几类也是个问题K取太大典型日代表性好但计算量成倍上涨K取太小储能充放电的策略会被平均化到不伦不类。我试下来4个典型日是精度和速度平衡得比较好的选择部分论文用3类也可以接受。5.2 收敛判据与早熟现象粒子群最典型的早熟现象就是收敛曲线在迭代前30%就变成一条水平线之后的迭代完全没有任何下降。遇到这种情况第一反应不应该是加大迭代次数而是检查是不是粒子多样性丢失太快。我在调试时试过几个方案在速度更新里加入随机扰动项、每隔一部分迭代把部分粒子重新随机初始化、引入模拟退火式的Metropolis准则接受劣解来跳出局部最优。效果最稳定的是“精英重启”策略——每50轮迭代保留gbest粒子随机初始化其他30%的粒子同时把这些新粒子的速度重置。这个策略不复杂但对打破早熟很有效。另外一定要固定随机种子做多次重复实验。EI论文的复现结果如果每次跑都不一样很难让审稿人相信这个算法是稳定的。我一般固定np.random.seed(0)跑5次独立重复看最优解的方差如果方差太大说明算法稳定性有问题。5.3 结果对比要对比什么复现论文时最让人头疼的是“怎么我的结果跟论文对不上”。坦白讲除非你能拿到作者的原始数据和超参数否则数值完全一致几乎不可能审稿人预期也不是这个。有效的对比方式是跑三个版本的算法标准PSO、改进PSO、再加一个常规的遗传算法GA做参照画收敛曲线对比收敛速度和最终目标值。如果改进PSO的最终年费用比标准PSO低2%~5%同时收敛速度快50%以上这已经是一个很好看的结果了。我在自己的复现里改进PSO比标准PSO的年费用降低约3.8%收敛到稳定值的迭代次数从420轮降到260轮左右这说明动态权重和约束修复策略确实起了作用。最后分享一个复现完这个模型后的感受改进粒子群算法在这类问题里真正的价值不是追求全局最优的数学保证而是在模型足够复杂、商用求解器不一定吃得住的时候给你一个能在可接受时间内找到好解的务实手段。调试过程中如果发现某一组参数下改进PSO还不如标准PSO别急着怀疑算法先回看约束处理是不是逼得太狠了。我自己的习惯是始终保留一个“基础参数对照”脚本每改一个改进机制就跑一遍同一组随机种子这样到底哪个改动起了作用一目了然。