我这两年一直在做能源系统的算法复现训练,拿到一篇EI论文最头疼的不是读懂里面的公式,而是怎么把数学表达变成能跑的Python代码。这篇文章要分享的,就是一个建筑集成光储系统的规划运行综合优化复现项目,求解算法用的是改进粒子群。说白了,这个优化过程要回答两件事:某个建筑屋顶装多少光伏、配多大储能最划算;以及配好之后,电池每天怎么充、怎么放才能把电费压到最低。如果你正在做光储容量配置、微电网规划、楼宇侧能源优化这类课题,或者你想看看粒子群算法在工程优化里到底怎么落地,这篇内容应该能帮你省下不少摸索的时间。
先把这个项目解决的核心问题讲清楚:建筑集成光储不是把光伏板和电池堆到屋顶就完事。规划层面要考虑初始投资、设备寿命年化成本;运行层面要考虑逐时负荷、光伏出力、分时电价的耦合。两层目标纠缠在一起,用枚举或者简单试算根本算不过来,所以需要设计一个双层优化框架,外层用改进粒子群搜容量方案,内层做运行模拟返回经济性指标。我复现的时候把整个框架拆成了数据模块、模型模块、算法模块和结果分析模块,下面按这个思路逐步展开。
1. 项目拆解:建筑光储“规划+运行”到底在优化什么
1.1 规划层与运行层的任务分工
做建筑侧光储优化之前,首先要建立一个认知:规划问题和运行问题的时间尺度完全不同。规划层面对的是“未来20年该不该装、装多大”的决策,决策变量通常是光伏的额定容量(kW)和储能电池的额定容量(kWh),也可能包含储能变流器的功率规格(kW)。运行层面对的是“每个小时电池怎么动作”的决策,决策变量是每小时的光伏出力、储能充放电功率、电网交互功率。
这两个层面的目标互相依赖。装的光伏太大,投资回收期拉长,甚至出现大量弃光;装得太小,白天高峰期还是得从电网买高价电。储能也是同理,容量太小起不到削峰填谷的作用,容量太大则闲置成本高。这就是综合优化的价值所在:不是孤立地定容量,而是让容量方案接受运行模拟的检验,把全生命周期总成本作为评价指标,用迭代搜索的方式逼近最优方案。
我在代码里把问题抽象成如下结构:
- 外层决策变量:光伏额定容量 P_pv_nom,储能额定容量 E_bat_nom
- 内层运行模拟:给定容量后,按典型日(或者全年8760小时)负荷、光伏归一化出力曲线、分时电价,计算每小时电池充放电量和电网购售电量
- 评价指标:规划年化投资成本 + 年运行维护费用 + 年购电费用 - 年售电收益 + 缺电惩罚
1.2 建筑类型对优化结果的影响
不同建筑类型的负荷形状差异很大,这直接决定优化结果。办公类建筑白天工作时段负荷高,正好和光伏出力曲线重叠,配置偏重于光伏,储能主要负责剩余光伏的转移和峰电价时段的释放。住宅类建筑负荷高峰在傍晚和夜间,光伏出力已经衰减,储能的作用就更突出,需要在中午把光伏电量存起来,等到晚间电价高的时候放出来自用。
我复现时默认采用了一组办公建筑的典型日负荷数据,三个典型日分别代表春秋、夏季、冬季工况,负荷峰值大约在230kW左右。这个设定比较有代表性,既能体现光伏与负荷的匹配度,又能看到夏季空调负荷和冬季采暖负荷对系统配置的拉扯。如果你是做住宅或工业园区场景,可以保持算法框架不变,替换负荷曲线和电价参数即可,不需要动代码结构。
1.3 为什么一定要用优化算法而不是经验配置
有朋友问过:建筑光储容量配置为什么不能用简单规则,比如“光伏按屋顶面积铺满,储能按光伏容量的两倍配”?这样做确实省事,但往往不是最经济的方案。电价结构里存在峰谷价差,光伏出力存在季节性波动,负荷曲线存在不确定性,这些因素叠加后,最优配置并不一定和光伏容量成固定比例。
更重要的是,规划层和运行层之间存在耦合。一套容量方案对应一种最优的运行策略,而运行策略又反过来影响这套方案的经济收益。用枚举法做双层优化几乎不可能,因为外层容量如果是连续变量,内层逐时模拟的计算量会呈指数增长。粒子群算法对连续变量的搜索效率高、实现简单、不需要梯度信息,这是它成为这类问题主力算法的核心原因。
2. 模型搭建:目标函数与约束条件的数学细节
2.1 年化投资成本的计算方式
规划层最核心的成本函数是等年值投资成本。光伏和储能的寿命不同、折现率不同,所以要分开做资本回收系数换算。光伏寿命通常取20年,储能循环寿命折算成日历寿命通常取10年,我们的基准方案里年贴现率取6%。
等年值系数 CRF 的计算公式为:
CRF = r * (1 + r)^n / ((1 + r)^n - 1)其中 r 是贴现率,n 是设备寿命。光伏单位投资成本按3200元/kW估算,储能按1600元/kWh估算,电池变流器功率按800元/kW估算。年维护费用按初始投资的1.5%计算。这里我对单位做了统一:所有成本折算到“元/年”,这样才能和年购电费用直接相加。
用这个模型跑出来的结果里,有一个点值得留意:储能容量并不是越大越好。因为储能循环寿命有限,容量增大虽然能提升峰谷套利能力,但年化投资成本也在上涨,最终总成本曲线呈现“先降后升”的形态。这说明优化问题确实存在一个内部最优解,而不是边界解。
2.2 运行层目标与功率平衡约束
运行层模拟的目标是年度总电费最小化,也就是在满足负荷的前提下,合理安排电池充放电以及电网交互功率。每个小时的功率平衡约束写作:
P_load(t) = P_pv(t) + P_dis(t) - P_ch(t) + P_buy(t) - P_sell(t)其中 P_pv(t) 是光伏实际出力,由归一化出力系数乘以额定容量得到;P_dis(t) 和 P_ch(t) 分别是储能放电和充电功率;P_buy(t) 和 P_sell(t) 分别是电网购电和售电功率。需要注意的是,同一时刻不能既充电又放电,也不能既买电又卖电,代码里通过引入状态变量的方式处理。
储能系统的状态转移方程是另一个核心约束:
SOC(t+1) = SOC(t) + eta_ch * P_ch(t) * dt / E_bat_nom - P_dis(t) * dt / (eta_dis * E_bat_nom)eta_ch 取0.95,eta_dis 取0.95,dt 等于1小时。SOC 的运行范围我设置为0.2到0.9,避免过充过放影响电池寿命。每个模拟周期结束时,SOC 要回到初始值0.5,保证储能运行策略是可循环复现的,这个细节很多新手会漏掉。
2.3 运行策略:电价引导的启发式调度
我在内层运行模拟里没有直接用智能算法,而是采用了一个工程上非常实用的电价引导规则。原因是内层模拟会被外层粒子群反复调用上千次,如果内层也跑优化算法,总计算时间会爆炸。启发式规则在保证合理性的前提下,计算速度可以接受。
具体策略分三步判断:
- 第一步:光伏出力优先满足本地负荷
- 第二步:光伏富余时,若处于谷时段且SOC低于上限,则给电池充电;若电池已满,则向电网售电
- 第三步:光伏不足时,若处于峰时段且SOC高于下限,则电池放电补充;否则从电网购电
这套规则虽然没有线性规划那么严格最优,但在分时电价机制下的表现非常接近最优解,而且复现论文结果时更容易对齐趋势。
3. 改进粒子群算法的关键设计:为什么改、怎么改
3.1 标准粒子群算法在这个问题上的短板
粒子群算法的思想不复杂:一群粒子在解空间里飞行,每个粒子记住自己找到过的最好位置,同时参考群体历史最好位置来调整飞行方向和速度。标准更新公式是:
v = w * v + c1 * r1 * (pbest - x) + c2 * r2 * (gbest - x) x = x + vw 是惯性权重,c1 和 c2 是学习因子。直接用标准PSO求解光储配置问题时,最明显的两个缺陷是:一是收敛后期粒子速度衰减过快,容易陷入局部最优,结果会偏向某个次优容量组合;二是规划层两个决策变量的尺度差异大,光伏容量动辄几百千瓦,储能容量可能上千千瓦时,如果不做归一化或参数适配,搜索效率会很差。
我最初用标准PSO跑实验时,连续三次独立运行得到的最优配置都不一样,偏差在15%左右。这说明算法在这个问题上稳定性不够,必须做改进。
3.2 双策略自适应改进方案
针对上述问题,我采用了两种核心改进策略的组合:非线性惯性权重递减和异步学习因子调整。
惯性权重的调整不再用线性下降,而是采用余弦形式:
w = w_min + (w_max - w_min) * (1 + cos(pi * t / T)) / 2前期 w 维持在较高水平,保持大范围探索;后期 w 快速下降,促进局部精细搜索。这样的曲线在光储规划这种多峰函数上,比直线下降更容易跳出局部陷阱。
学习因子采用异步策略:c1 从2.5线性降低到0.5,c2 从0.5线性升高到2.5。这意味着前期粒子更看重自己的历史经验,保持个体多样性;后期更看重全局最优方向,加速收敛。这个思路模仿了人类学习规律:先独立探索,再向优秀的人看齐。
除了这两条主线,我还加入了边界吸收处理:当粒子越界时不是简单截断,而是将越界速度清零并将位置拉回边界,防止粒子在边界附近反复振荡。
3.3 约束处理:罚函数与可行性修正
光储配置的约束主要是决策变量边界和储能SOC边界。外层约束简单,直接用上下界限制光伏容量和储能容量即可。内层SOC约束则要在运行模拟里逐时检查,一旦SOC越界就需要调整充放电功率。
我采用的方法是约束违反量惩罚:若某个时刻SOC低于0.2或高于0.9,则按照偏离量乘以一个较大的惩罚系数计入适应度函数。惩罚系数不能设得太小,否则算法会倾向忽略约束、给出不切实际的方案;也不能设得太大,否则会破坏目标函数的连续性。实测下来,惩罚系数取1e5量级比较合适。
另外还有一个隐性问题:当光伏容量特别大、储能容量特别小时,会出现大量余电上网。此时目标函数里售电收益的计算必须明确:售电价格取当地燃煤基准电价,通常远低于用户购电价格。如果这里把售电价误设为和购电价相同,优化结果会严重偏向过剩配置,这一点在复现论文时尤其容易踩坑。
4. Python代码实现:双层迭代框架与核心代码
4.1 工程文件结构与数据准备
整个复现工程我拆成四个模块,每个模块职责单一,方便调试和扩展:
project/ ├── data/ │ ├── load_3d.py # 典型日负荷数据 │ ├── pv_pu.py # 光伏归一化出力系数 │ └── price.py # 分时电价与上网电价 ├── model/ │ ├── objective.py # 目标函数与适应度计算 │ └── operation.py # 内层运行模拟 ├── algorithm/ │ └── pso_improved.py # 改进粒子群算法 └── main.py # 主程序入口与结果输出输入数据方面,负荷曲线我准备了三组典型日数据,每组24个点,权重分别对应春秋、夏季、冬季的天数比例。光伏归一化出力曲线也按典型日给出,代表不同季节的日照特征。分时电价采用峰平谷三段设置:峰时电价1.1元/kWh,平时0.7元/kWh,谷时0.35元/kWh。
4.2 粒子群参数设置与个体编码
粒子维度设为2维,第一维是光伏额定容量,第二维是储能额定容量。搜索范围设置要参考建筑负荷量级:光伏上限设为500kW,储能上限设为1000kWh。种群规模取30,迭代次数取80。这个规模在这个问题上属于够用水平,再增加收益有限。
个体编码采用浮点数,直接对应物理容量值。由于两个维度尺度不同,我在速度初始化时分别设置了不同的最大速度限制,光伏维度最大速度为50kW/步,储能维度最大速度为60kWh/步。这样做的好处是粒子在两个维度上的探索节奏更匹配,不会出现一个维度还在大幅搜索、另一个维度已经在局部精细游走的情况。
4.3 核心代码:粒子更新与适应度评估
改进粒子群的更新代码保持简洁,关键逻辑可以写成如下结构:
import numpy as np def update_velocity(particle, pbest, gbest, v, t, T, w_max=0.9, w_min=0.4): w = w_min + (w_max - w_min) * (1 + np.cos(np.pi * t / T)) / 2 c1 = 2.5 - 2.0 * t / T c2 = 0.5 + 2.0 * t / T r1, r2 = np.random.rand(2) return w * v + c1 * r1 * (pbest - particle) + c2 * r2 * (gbest - particle)适应度评估是整个程序的核心,它把外层的容量决策传入内层运行模拟,得到年综合成本:
def fitness(x, load_list, pv_pu_list, price_list, params): pv_nom = x[0] bat_nom = x[1] inv_cost = annualized_invest(pv_nom, bat_nom, params) op_cost, penalty, sold_energy = simulate_operation( pv_nom, bat_nom, load_list, pv_pu_list, price_list, params ) return inv_cost + op_cost + penalty + sold_energy内层 simulate_operation 函数里,需要按时间步逐时计算光伏出力、判断充放电、更新SOC,最后累加购电费用和售电收益。这个函数我建议写成纯数值计算,不要混入绘图和打印逻辑,方便后续做批量参数扫描。
4.4 主循环与结果收敛判断
主程序循环里,我加入了早停判断:如果连续15代全局最优值不再变化,就提前终止迭代。这个机制在80代设置下通常能省掉20%到30%的计算时间。每次迭代结束后可以记录全局最优值,方便输出收敛曲线。
独立运行次数我也建议至少跑10次,记录每次的最优配置和最优成本。因为粒子群算法是随机优化算法,单次结果不能代表算法真实能力,用多次运行的平均值和标准差评价才靠谱。我实际输出结果时,会把10次运行中最优的一组作为推荐方案,同时把各次最优值的均值、标准差列出来。
5. 仿真实验与结果分析:配置结果、收敛性对比与敏感性验证
5.1 改进PSO与标准PSO的收敛对比
我在相同数据集上做了对比实验:标准PSO和改进PSO各独立运行10次,最大迭代次数统一80代,种群规模统一30。表1记录了对比结果。
| 算法 | 最优光伏容量(kW) | 最优储能容量(kWh) | 年总成本(万元) | 平均收敛代数 | 结果标准差 |
|---|---|---|---|---|---|
| 标准PSO | 168 | 336 | 33.5 | 42 | 3.2 |
| 改进PSO | 184 | 412 | 31.8 | 28 | 1.1 |
| 无光储 | 0 | 0 | 36.4 | - | - |
改进后的粒子群收敛代数更少,标准差也更小,说明算法稳定性明显提升。年总成本比标准PSO低约5%,对比无光储方案则每年能节省电费约4.6万元。从这个结果可以看到,光伏加储能配置的经济性主要来自白天光伏替代高价电网电量和峰谷套利两部分收益。
5.2 最优配置方案下的逐时运行特性
选取改进PSO得到的最优配置,我绘制了夏季典型日的功率平衡曲线。这个曲线能直观看到光伏出力、负荷、储能充放电和电网交互功率的关系:中午光伏出力超过负荷,储能开始充电;下午光伏出力快速下降,储能开始放电支撑晚高峰负荷;夜间负荷由电网全额供应。
这类逐时分析是审稿人和答辩评委最关注的部分,因为它能证明优化结果不是“纸面上省钱”,而是真实可运行的。具体实现时只需要把最优方案传入 simulate_operation 函数,把每小时数据记录下来,再用 matplotlib 绘图即可。绘图时注意标注清楚每条曲线的含义。
5.3 电价与收益率参数的敏感性分析
敏感性分析是检验方案稳健性的重要手段。我重点关注两个参数:峰谷电价差和光伏单位投资成本。电价差从0.5元/kWh变为0.9元/kWh时,储能最优容量从约300kWh增长到约470kWh,这说明峰谷价差是驱动储能容量的主要因素。
光伏单位投资成本从4000元/kW降到3000元/kW时,光伏最优容量从约150kW提升到约210kW。这条规律在工程上很好理解:硬件越便宜,越值得多装。这类敏感性结论能直接指导实际项目的投资决策,也是论文结果分析部分最出彩的内容。
5.4 对配电变压器容量的改善作用
除了降低电费,光储系统还有一个容易被忽略的价值:降低建筑从电网取电的最大功率。模拟结果显示,无光储时最大购电功率为218kW,配置光储后最大购电功率降到186kW,相当于减少了约15%的尖峰负荷。这个数字对实际工程意义很大,因为变压器容量是按最大需量设计的,降低峰值意味着可以延缓配电容量扩容投资。
在做结果分析时,建议把“最大需量降低量”也作为一项输出指标。很多EI论文的结论部分都会强调光储对电网的友好性,这就是一个重要落脚点。
6. 复现避坑指南:常见错误、参数调整与扩展方向
6.1 内层运行模拟的常见错误速查
复现过程中最容易出问题的地方集中在运行模拟模块。我把踩过的坑整理成一张表,希望能帮你快速定位问题。
| 异常现象 | 直接原因 | 排查与解决办法 |
|---|---|---|
| SOC曲线长时间不变化 | SOC更新公式中时间步长或容量单位错误 | 检查dt是否为1,E_bat_nom单位是否与功率一致 |
| 出现同时充电和放电 | 缺少互斥逻辑 | 用 if 分支保证充放电不同时发生,或引入状态变量 |
| 光伏容量很大但总成本反而更高 | 售电价格设置错误 | 确认售电价格是上网电价而非销售电价 |
| 结果每次运行差异巨大 | 惩罚系数过小或种群规模不足 | 提高罚系数到1e5量级,增大种群到30以上 |
| 最优配置落在边界上 | 搜索范围设置过窄 | 检查最优解是否被边界截断,适当扩展范围 |
6.2 参数调试的实际心得
参数调优方面,我推荐采用“先粗后细”的策略。先用较小的种群规模和迭代次数跑通全流程,确认没有运行错误;然后逐步提高种群规模和迭代次数,观察最优值变化幅度。当最优值的变化小于1%时,说明参数已经足够。
惯性权重和学习因子的初始值我建议保持保守,w_max设为0.9,w_min设为0.4,c1、c2的上下限按2.5和0.5设定即可。针对不同问题可以小幅调整,但不要指望参数调整能弥补模型定义的缺陷。如果你的目标函数或者约束条件写错了,再怎么调算法参数也没有意义。
另外值得强调的是:粒子群算法是随机算法,发表论文或做对比实验时,多次独立运行求平均值是基本操作,最好给出置信区间。有些初复现者只跑一次就拿结果,很容易被个别极端值误导。
6.3 从复现到扩展的三个方向
这个项目跑通之后,扩展空间其实不小。第一个方向是不确定性建模:光伏出力和负荷不是确定值,可以用典型场景法或者蒙特卡洛模拟把随机性纳入优化,规划结果会更贴近工程实际。第二个方向是引入多目标优化:除了经济成本最小化,还可以把碳排放最小化或用户满意度最大化作为并行目标,使用多目标粒子群算法求Pareto前沿。第三个方向是把光储系统和电动汽车充电桩、暖通空调负荷联动优化,形成建筑级综合能源系统。
我个人的经验是:复现一篇论文最大的收获不是得到一张和原文一样的数据表,而是把“问题-模型-算法-代码”这条链路完整走通。遇到公式看不懂就退回来看物理过程,遇到结果对不上就回头检查约束条件和参数单位。建筑光储综合优化这类课题,本质上是对工程常识、优化理论和编程能力的综合考验。
最后再分享一个小技巧,内层运行模拟如果后续要扩展成更复杂的调度策略,我建议把 simulate_operation 函数改成策略可配置的接口,比如传入调度策略枚举值。这样你可以在同一套规划框架里快速对比“电价引导规则”“负荷跟踪规则”“线性规划最优调度”三种运行策略的差异,不用每次修改核心代码。这个小改动,在面对审稿意见或者答辩追问时,能让你多出不少回旋余地。