ARTICLE DETAIL

资讯详情

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

改进粒子群算法求解建筑光储系统规划运行综合优化:Python复现实践

改进粒子群算法求解建筑光储系统规划运行综合优化:Python复现实践 最近在复现一篇EI检索的论文题目翻译过来是《基于改进粒子群算法求解的建筑集成光储系统规划运行综合优化方法》。原论文的思路很清晰把屋顶光伏、储能电池和建筑负荷揉成一个优化问题用改进粒子群算法在两个层面同时寻优既决定系统装多大又决定电怎么用。论文里的公式和流程图都有唯独没给可运行的代码。这类EI复现的活儿我接过不少大多数时候论文发表时只提供最终数据和图表复现全靠自己补全细节这篇也不例外。我前后花了大概两周时间用Python从零搭了一套完整的复现工程中间踩了不少坑也整理出一些值得分享的经验。这篇博文不打算逐行贴论文公式而是想讲清楚三件事这个优化问题到底在求什么、改进粒子群改在哪里、以及Python代码落地时有哪些容易翻车的地方。如果你正打算复现类似的能源系统优化论文或者想学习改进粒子群算法怎么应用到实际工程问题上这篇应该能帮你省下不少试错时间。1. 复现前必须先拆解的问题模型建筑光储系统在优化什么规划运行综合优化这七个字里其实藏着两层意思。规划指的是决策设备容量光伏到底建多少千瓦储能电池配多少千瓦时运行指的是决策设备投运后的逐时段出力一天24个小时里储能是充电还是放电、从电网买多少电、光伏富余时卖不卖。这两个层面不是孤立的容量定得大初投资高但后续运行时的峰谷套利收益也大容量定得小初投资省了但购电成本压不下来。规划影响运行运行反馈回规划所以必须联合求解。如果先定容量再算运行等于把一个耦合问题强行劈成两半通常只能得到次优解。这是原论文强调综合优化的根本原因也是复现时最需要尊重的模型结构。我见过一些复现代码只优化容量、运行策略直接按固定比例分配结果跑出来的配置方案完全没体现出储能的价值那其实已经偏离了题目本意。1.1 系统物理构成与决策变量建筑集成光储系统可以简化为五个节点光伏阵列、储能电池组、建筑负荷、电网公共连接点和能量管理控制器。控制器的职责就是每个时段决定功率流向光伏出力先满足本地负荷富余部分要么存进电池、要么卖给电网光伏不够时电池放电补缺还不够就从电网买。决策变量也顺理成章分成两层。规划层是设备容量光伏装机容量、储能额定容量、储能额定功率运行层是逐时段的储能充放电功率、购电功率、售电功率。我把常见论文的变量表整理成了这样层级决策变量符号单位规划层光伏装机容量P_pvkW规划层储能额定容量E_esskWh规划层储能额定功率P_esskW运行层逐时段储能功率P_es(t)kW运行层逐时段购电功率P_buy(t)kW运行层逐时段售电功率P_sell(t)kW运行层的时间粒度通常取1小时一天24个时段如果论文做典型日扩展还可能把春夏秋冬各取一个典型日那就是4×24个时段。变量维度大概在几十到几百之间属于中等规模的非线性优化问题正是粒子群算法的舒适区。如果用线性规划求解光伏出力曲线的非线性和储能充放电效率的耦合反而要做一堆线性化处理建模成本很高用遗传算法也能解但种群收敛慢参数调起来更繁琐。1.2 目标函数一个典型的综合优化表达式目标函数拆开其实就四块等年值初投资成本、年运行维护成本、年购电成本、年售电收益。目标是最小化前三者、最大化最后一项习惯上统一写成最小化形式。初投资成本不是简单的设备价格乘以容量需要乘以等年值系数把一次性投资摊到每一年。等年值系数的计算依赖折现率和设备寿命原论文常见取值是折现率5%、光伏寿命25年、储能寿命15年储能到期后还有更换成本。我在复现时把这些全部参数化方便后续做敏感性分析。这里有个经常被忽略的细节储能寿命比光伏短规划周期跨25年时储能往往要更换一到两次更换成本如果漏算最优方案会明显偏向储能扩容结果失真。还要注意不同论文对综合优化的定义不完全一样。有些论文把碳排放或自给率也纳入目标函数形成多目标优化我复现的这篇是加权求和转成单目标代码处理起来相对简单。如果你手头那篇是多目标就需要额外实现Pareto前沿和非劣解排序那是另一个工作量级。1.3 运行约束与耦合关系约束是复现中最容易看漏的部分因为论文里常常一笔带过。我梳理下来至少包含五类功率平衡约束任意时段光伏出力加储能放电加购电等于负荷加储能充电加售电。储能SOC递推约束每个时段结束更新荷电状态且SOC必须落在上下限内典型是0.1到0.9。充放电功率上下限储能功率不能超过额定功率且理论上不能同时充电和放电。光伏出力上限每个时段光伏出力不能超过辐照对应的理论功率。购售电功率上限取决于变压器容量和并网协议。前两类约束是时序耦合的牵一发动全身。某个时段充电多了后面所有时段的SOC都会受影响SOC越界又反过来限制当前时段的充放电功率选择。复现时最常犯的错就是把它们当成独立约束逐一检查没有意识到这是一个带时序递推的约束网络。这也是为什么后面要专门讨论约束处理策略——在粒子群框架里时序约束不是简单把变量限制在一个矩形可行域内而是需要逐时段递推校验。2. 从标准PSO到改进PSO算法升级的内在逻辑粒子群算法的核心逻辑不复杂一组粒子在搜索空间里飞行每个粒子有位置和速度每代根据自身历史最优和全局最优调整方向。惯性权重控制延续旧方向的程度学习因子控制飞向个体最优和全局最优的吸引力。标准版本二三十行Python就能写出来跑一些简单测试函数效果还不错。但直接把标准粒子群扔到光储规划问题上很快就会发现不对劲。2.1 标准粒子群在光储问题上的短板第一个短板是早熟收敛。粒子群迭代到后期种群多样性快速下降所有粒子都挤在全局最优附近如果这个全局最优其实是局部最优算法基本出不来。光储规划的目标函数有大量局部极值我试过用标准粒子群跑连续几次实验的最终配置方案差距很大有的方案储能容量小到几乎不起作用明显就是卡在了局部陷阱里。第二个短板是混合维度问题。规划层变量和运行层变量对目标函数的敏感度完全不同。储能容量变动1%对目标函数的影响很小而运行层的充放电策略变动1%可能直接让SOC违反约束。标准粒子群对所有维度用同一套速度参数结果就是运行层维度被过早锁定后续迭代几乎只在调规划层变量搜索效率很低。第三个短板是约束难满足。粒子初始位置和速度是随机生成的大量粒子从一开始就落在不可行域。如果后续的约束处理策略设计不好粒子会在不可行域里反复徘徊浪费大量迭代次数。2.2 我采用的三个改进点及理由针对上述问题我采用了三个在EI论文里常见的改进策略效果比较稳。一是混沌映射初始化。用Logistic混沌序列生成初始种群替代均匀随机。混沌序列在0到1区间分布更均匀、相关性更低能在初始阶段就铺开搜索范围。光伏容量和储能容量这类决策变量对初始位置敏感混沌初始化能明显减少运气成分。我在同样参数下分别用随机初始化和混沌初始化跑了20次实验混沌初始化跑出的最优解更稳定标准差明显更小。二是自适应惯性权重。标准粒子群的惯性权重要么固定要么随迭代次数线性衰减这两种都没考虑种群当前的真实状态。我的做法是用粒子群平均适应度与全局最优适应度的差值来度量种群聚集程度聚集程度高说明大家挤在一起增大惯性权重鼓励探索聚集程度低说明粒子分散减小惯性权重鼓励局部精调。这个策略比线性衰减更灵活实测收敛速度略慢一些但最终解质量更高。三是变异与重新初始化操作。设置一个变异概率每迭代若干代随机挑一部分粒子做位置扰动同时监控全局最优的更新情况如果连续若干代没有变好就把一部分粒子丢到搜索空间远处重新探索。这个策略专门用来对抗早熟收敛效果非常直接。除了这三个改进点边界处理也值得单独说。速度越界时我用clip裁剪到上限位置越界时采用一种叫吸收越界的处理方式把越界分量拉回边界同时把对应维度的速度分量置零避免粒子贴在边界上来回震荡。我对比过单纯clip速度会让粒子长期贴着边界滑行吸收越界处理则能让粒子更快跳回可行区内部。2.3 约束处理不能只靠罚函数罚函数是粒子群最省事的约束处理方式但罚函数系数很难调。系数太小不可行解罚得不够重最优解可能落在不可行域里系数太大可行域边缘的目标函数变成陡峭悬崖粒子不敢靠近边界。比如SOC上限约束罚系数过大会让储能满充的经济性被严重扭曲明明工程上合理的方案算法却认为成本高得离谱。我在复现中用的是可行性优先策略也叫约束支配规则。具体规则是比较两个粒子时可行解永远优于不可行解如果两个都可行比较目标函数值如果两个都不可行比较约束违反总量。这个准则不需要调罚系数最终解天然满足约束。这是Deb等学者在约束进化优化领域提出的经典比较准则我直接借用过来落地效果比罚函数稳定得多。这个选择的收益在后期调试时特别明显不管粒子群参数怎么改可行解和不可行解的优先级关系始终明确不会出现罚系数没调好导致结果忽好忽坏的玄学状况。3. Python工程实现代码结构、核心函数与调试要点复现过EI论文的人都有体会论文里的伪代码信一半就行真正难的是把公式落成可运行的Python代码。整个复现我全程用Python完成核心依赖numpy做数值计算matplotlib做结果可视化。下面按工程实现的角度说一些实用细节。3.1 工程目录与类结构设计我的工程目录长这样energy_system_optimization/ ├── parameters.py # 设备参数、电价、负荷、光伏数据 ├── model.py # 目标函数与约束检查 ├── pso_improved.py # 改进粒子群算法主代码 ├── run_optimization.py # 主程序入口 ├── utils.py # 数据处理与绘图辅助 ├── results/ # 结果输出 └── figures/ # 图表输出parameters.py里定义了一个SystemParams类字段包括光伏单价、储能单价、折现率、寿命年限、负荷曲线列表、分时电价列表、光伏辐照序列全部用字典和列表集中管理。数据质量的优劣直接决定复现结果是否合理我第一次跑的时候随便填了一组假数据结果最优解在工程上完全站不住脚。后来换成贴近实际的数据优化结果才变得有参考价值。所以复现的第一步不是写算法而是把参数表仔细核对清楚。3.2 目标函数与约束检查的代码要点目标函数实现本身不复杂但有一个关键决策写成单次调用还是批量向量化。粒子群50个粒子、迭代300代就是15000次函数调用如果内部再嵌套逐时段的Python循环运行时间会非常感人。我的做法是至少把单个粒子内部的24时段序列用numpy数组整体计算尽量减少Python层循环。目标函数的核心结构大致是这样def objective(x): p_pv, e_ess, p_ess, *operation x # operation 是 24 个时段的储能充放电功率 # 1. 计算等年值初投资 inv_cost equal_annual_cost(p_pv, e_ess, p_ess) # 2. 逐时段计算购电、售电费用累加运维成本 # 这里用 numpy 数组一次性算出全年费用 # 3. 检查 SOC 递推是否越界累加违反量 violation check_soc_constraints(operation, e_ess) return total_cost, violationSOC状态递推我单独写成一个子函数每一步都检查是否越界越界就累加违反量最后统一交给约束支配准则处理。这个设计让优化目标和约束求解完全解耦算法主循环不需要关心约束细节只在比较粒子时使用violation值改起来非常方便。3.3 改进粒子群主循环的实现细节改进粒子群主循环里特别关注三件事pbest和gbest的更新必须用可行性优先规则每轮根据群体聚集程度计算自适应惯性权重每间隔若干代执行一次变异操作。核心骨架大概是这样的for iteration in range(max_iter): # 1. 根据当前群体聚集程度计算自适应惯性权重 w adaptive_inertia(fitness_current, gbest_fitness) # 2. 更新粒子的速度与位置 velocity w * velocity c1 * r1 * (pbest - position) \ c2 * r2 * (gbest - position) position position velocity # 3. 解决边界越界吸收越界处理 position, velocity absorb_boundary(position, velocity, bound_low, bound_up) # 4. 计算目标函数和约束违反量 fitness, violation evaluate_population(position) # 5. 用约束支配规则更新 pbest 和 gbest update_best_by_feasibility(position, fitness, violation) # 6. 变异与重新初始化部分粒子 if iteration % mutate_interval 0: mutate_population(position, velocity)实战里有个细节值得说粒子速度初值对结果极其敏感。我第一次给速度上下限和位置上下限设成一样宽结果粒子前几代全部飞出搜索空间适应度惨不忍睹。后来把速度上下限设定为位置跨度的10%到20%前期探索和后期收敛都恢复正常。这类半经验参数论文里基本不会写只能自己试出来。3.4 从装环境到跑通的完整节奏考虑到不少读者刚开始接触Python环境这块多说两句。Python 3.8及以上版本跑这个项目比较稳妥依赖安装统一用pippip install numpy matplotlib pandas如果本机存在多个Python版本一定要确认pip对应的是哪个解释器。我有一次把pandas装到了系统自带的Python 2环境里项目脚本却用的是Python 3import一直提示找不到模块排查了半天才发现不是代码问题而是环境串了。离线环境装不了的话可以到对应版本的whl文件手动安装注意numpy和Python版本有对应关系别随便下错版本。4. 结果分析与可视化从收敛曲线到运行策略算法跑完不等于复现完成。EI复现是否靠谱核心看三件事收敛曲线是否合理、最优配置是否落在工程可行区间、运行策略是否体现峰谷套利逻辑。任何一条不符合第一反应应该是代码有bug而不是怀疑论文。4.1 收敛曲线与优化过程检查收敛曲线用matplotlib画起来很简单import matplotlib.pyplot as plt plt.plot(range(1, max_iter 1), best_record, linewidth2) plt.xlabel(迭代次数) plt.ylabel(总成本 / 元) plt.grid(True) plt.xticks(range(0, max_iter 1, 25)) # 每25代显示一个刻度 plt.show()画图时有个常见的坑横坐标刻度太密集。迭代几百次时如果每代都标刻度横轴会糊成一团这也是网上很多人搜python画图横坐标太密集的原因。解决办法很简单用plt.xticks间隔抽样或者用MaxNLocator限制刻度数量。什么样的收敛曲线算合理我的判断标准是前期快速下降中期进入平台区后期偶尔有小幅跳跃。后期的小跳跃来自变异操作说明有粒子跳出了局部最优重新搜索是好现象。如果曲线一路大起大落多半是速度范围太大或者变异概率过高需要回调参数如果曲线平滑得像一条直线下降反而要警惕是不是初始种群就落在某个局部区域里。4.2 最优配置与全年经济性分析基于我整理的一套典型数据优化结果大致是光伏装机187.6千瓦储能容量420千瓦时储能额定功率120千瓦等年值总成本比不配置光储的基准方案降低约12.6%投资回收期大约7到8年。这是一组符合常识的结果光伏装机偏大但不过分储能容量远大于功率说明系统主要靠峰谷价差获利而不是靠功率快速响应。这里必须提醒一点具体数值依赖几十个参数不同论文的数据集差异很大不要拿来直接对数字。真正重要的是模式和量级储能容量和额定功率的比值通常在2.5到4之间光伏装机不应超过变压器容量上限回收期不能短得不现实。如果你的复现结果里储能容量和功率几乎一样大或者回收期只有两年那大概率是目标函数漏项或参数设置有问题。4.3 典型日运行策略图表典型日运行图是复现报告里最关键的图。横轴是24个时段纵轴是功率分别画光伏出力、负荷、储能充放电、购电四条曲线。光伏出力随太阳辐照呈钟形曲线储能策略应该在低价时段充电、高价时段放电购电曲线在高峰时段明显压低。看到这样的图才敢说复现基本成立。我在画图时把功率轴统一成千瓦负值表示储能充电正值表示放电不同曲线用不同颜色区分并加上图例。如果运行策略图看起来是混乱的比如储能半夜满充、白天高峰还在充电那说明代码里的目标函数或约束逻辑多半有问题先回到3.2节检查SOC递推而不应该继续调算法参数。5. 复现中的坑与验证心得最后一部分专门讲复现过程中最容易踩的坑和排查链路这部分属于纸上不会写的经验。5.1 环境与依赖Python版本、numpy、matplotlib环境问题看似无关紧要却能毁掉一整个下午。我的建议是每个项目独立建虚拟环境python -m venv .venv source .venv/bin/activate # Windows 下为 .venv\Scripts\activate pip install numpy matplotlib pandas第二个坑是matplotlib的显示后端。如果你在远程服务器上跑项目没有图形界面plt.show()会直接报错或挂起。这时候要么改成plt.savefig(figures/result.png, dpi300)保存图片要么在代码开头设置Agg后端。网上很多人搜python环境配置python连接cmd之类的词多半就是在这些环节卡住了。另外提醒一点numpy和matplotlib版本别追最新尤其别和pandas版本冲突。我遇到过一次numpy版本过新导致scipy依赖报错的情况最后锁回老版本才消停。复现项目追求的是稳定跑通不是用最新特性。5.2 验证复现正确性的三条经验第一条解析解对照。把光伏容量固定为0、储能容量固定为0目标函数应该退化成纯购电成本手算一下年耗电量乘以平均电价跟程序输出对一下。这一步都不对的话后面全白搭。第二条约束激进测试。把储能容量设成极小值比如1千瓦时运行层几乎没有峰谷套利空间最终总成本应该非常接近无储能方案。如果结果反而大幅省钱那约束或目标函数肯定有漏项。第三条基准方案对比。光储方案的总成本必须低于纯电网供电方案否则算法没有找到真正的改进方向。我一开始出现过光储方案反而更贵的情况排查后发现是储能更换成本被重复计算了修掉之后结果才合理。5.3 其他容易栽跟头的细节SOC公式的更新顺序不能搞反。正确做法是先根据本时段决策功率计算SOC_{t1}下一时段的功率约束再基于SOC_{t1}校验。顺序一倒相当于凭空引入能量SOC会悄悄越界。初始种群全都是不可行解时要留退路。如果某代没有任何可行粒子我允许按约束违反量排序选择最好的不可行粒子作为临时gbest否则算法前几十代可能一直在做无用搜索。这个策略不影响最终可行解的质量但能明显加速搜索。浮点误差也要注意。SOC递推反复累加会产生微小漂移0.9000001这种值可能触发越界判断。我的处理是统一保留三位小数同时把SOC上限判断留2%的工程裕量用0.88作为上限而不是0.9。这类处理在论文里通常不会写但实际运算非常必要。最后说点做这行的心得。EI论文复现这件事真正难的往往不是算法本身而是把论文里省略掉的细节一一补齐。我复现这篇建筑集成光储系统规划运行综合优化方法的时候光是储能SOC约束和约束支配策略就来回调试了两天一度怀疑自己改进粒子群的代码写错了。后来通过把时间粒度缩到单日24时段、用解析解校验单个粒子才逐步把问题定位到边界处理和浮点精度上。这种排错思路跟算法本身一样值得积累。如果你也正准备复现类似的优化类论文我建议先跑通小规模算例、逐模块验证再上全规模数据整个过程会顺畅很多。
返回列表