拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

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

改进粒子群算法求解光储系统规划运行综合优化:模型、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 × (1+r)^L / ((1+r)^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(t+1) = 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_min

w_max取0.9,w_min取0.4,alpha取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_state=42,并且跑了多次验证聚类结果的稳定性。

from sklearn.cluster import KMeans import numpy as np # X_daily shape: (365, 48),前24维是负荷,后24维是光伏 kmeans = KMeans(n_clusters=4, random_state=42, n_init=20).fit(X_daily) labels = kmeans.labels_ weights = np.bincount(labels, minlength=4) / 365.0 typical_days = np.array([X_daily[kmeans.labels_ == i].mean(axis=0) 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,别急着怀疑算法,先回看约束处理是不是逼得太狠了。我自己的习惯是始终保留一个“基础参数对照”脚本,每改一个改进机制就跑一遍同一组随机种子,这样到底哪个改动起了作用,一目了然。

返回列表