ARTICLE DETAIL

资讯详情

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

改进粒子群算法在建筑光储系统规划运行优化中的Python实现

改进粒子群算法在建筑光储系统规划运行优化中的Python实现 前阵子接手了一个不大不小的题目复现一篇EI期刊里基于改进粒子群算法的建筑集成光储系统规划运行综合优化方法并且用Python代码完整落地。这个题目最折磨人的地方不在算法本身而在于“规划”和“运行”两个词是绑在一起的——如果你只是照着标准粒子群改几个参数就往里跑大概率会得到一个看起来很科学、实际一算就亏的方案。这篇文章把我从模型搭建、算法改进、代码实现到调参排错的全过程写下来。核心关键词是EI复现、改进粒子群算法、建筑集成光储系统、规划运行综合优化代码层面完全用Python实现。适合正在做光储容量规划、微电网优化调度、或者准备复现类似期刊论文的读者参考即使你只对其中一块感兴趣也能直接跳到对应章节抄作业。1. 先弄清楚题目的边界规划与运行是什么关系1.1 规划层和运行层各管什么事建筑集成光储系统说白了就是给一栋楼配上屋顶光伏、储能电池让它和电网之间既能买电也能卖电。这个系统可以拆成三个物理对象建筑负荷、分布式光伏、电池储能。规划层负责决定“装多大”。光伏装多少千瓦、储能配多少容量和功率这三个数字一旦定下来初投资就锁死了。运行层负责决定“怎么用”。具体到每个小时储能是充电还是放电、从电网买多少电、光伏余电卖不卖。一个是长期决策一个是短期调度时间尺度完全不同。这里有个容易被忽略的关键点规划变量和运行变量是两种性质完全不同的东西。规划变量是连续容量值可以编码成粒子位置运行变量是24个时间点的功率分配本质上是时序调度问题。两者之间的耦合关系是规划变量定了运行调度才有可行域运行调度的经济效果反过来又决定规划方案划不划算。分开算行不行行但结果会差不少。我在实际复现中有个很深的体会如果你先在规划阶段假设一个固定的运行模式比如“光伏只白天自发自用储能只做削峰填谷”然后拿这个假设去算最优容量再把容量代入真实运行优化你会发现算出来的项目收益和容量方案都对不上。原因很简单——容量变了最优调度策略也变了固定假设站不住脚。1.2 为什么“综合优化”比分开算更靠谱举一个具体例子说明。某栋商业建筑白天有办公负荷晚上负荷很低屋顶光伏在中午出力最大。如果规划阶段默认“光伏只支持白天自用”那它就会倾向于把储能容量配得很小因为中午光伏可以全额自消纳储能“没有用武之地”。但实际运行中电网往往有峰谷电价。晚上高峰电价时段如果储能能在白天多存点电、晚上放出来套利收益会明显增加。这时候储能容量的价值就变了。反过来多配的储能到底值不值取决于电价差、充放电效率、投资成本以及它每天到底能被调度得多聪明——这就是运行层的信息。不跑运行优化你根本不知道那几百千瓦时的电池实际上能产生多少收益。所以综合优化的本质是嵌套优化外层每提出一组规划容量内层就跑一次最优化调度返回最优运行成本再回到外层更新方案。这个结构也叫双层优化或者嵌套优化。复现这篇文章时真正的工作量有一大半花在这套嵌套结构写得好不好上。2. 改进粒子群算法为什么要改改了哪些地方2.1 标准PSO在光储规划问题上的三个痛点先摆标准粒子群公式。每个粒子有位置向量x和速度向量v迭代更新方式如下v_i w·v_i c1·r1·(pbest_i - x_i) c2·r2·(gbest - x_i)x_i x_i v_i其中w是惯性权重c1、c2是加速系数r1、r2是随机因子pbest是个体历史最优gbest是全局最优。这个算法在连续优化问题上效果很好但放到光储规划这种多约束、非线性的场景里问题立刻暴露出来。第一标准PSO的惯性权重一般是线性固定递减前期大范围搜索和后期局部开发的比例是死的问题规模一换参数就要重新调。第二早期w比较大的时候粒子搜索范围广很容易越界。很多人的第一反应是遇到越界就裁剪到边界上这个做法有坑——粒子会大量堆积在边界种群多样性直接崩掉。第三储能容量和运行调度之间是典型的非线性耦合目标函数里还带惩罚项曲面上有多处“假洼地”标准PSO很容易早熟收敛到一个局部最优。复现论文做对比实验的时候你一看收敛曲线就明白差距非常直观。2.2 本次改进的具体设计我带复现时用的是一套组合拳混沌初始化、自适应惯性权重、异步学习因子、边界反射和小概率变异五个改进叠在一起。混沌初始化说的是用确定性但伪随机的序列代替rand去生成初始种群。我用的是Logistic映射xₖ₊₁ 4·xₖ·(1 - xₖ)。这个式子生成的序列在(0,1)区间内分布更均匀不像随机数那样容易出现扎堆。对多峰优化问题初始种群分布越均匀找到全局最优的概率越大。这一步改进的代码实现非常便宜几行就能写完但效果很大。惯性权重改成非线性凸递减。公式是w w_min (w_max - w_min)·(1 - (t/T)²)t是当前迭代数T是最大迭代数。这么写的意思是前面一小半迭代权重还保持在高位能大范围撒网侦察越到后面下降越快强化局部精细搜索。异步学习因子的思路是让c1从2.5递减到0.5c2从0.5递增到2.5。前期多学个体历史经验、保持探索性后期多向全局最优看齐、加速收敛。这个方向是经过验证的比自己随便拍两个常数要稳。还有一个特别实用的改进越界处理用边界反射。粒子碰到上限时不是被截断而是按“镜面反射”的方式折返回搜索空间内部。比如允许区间是[0,100]粒子飞到105裁剪的结果是固定在100反射的结果是变成95。这一步看着不起眼实践里非常重要。裁剪会让边界点堆满粒子后期搜索彻底失去多样性反射则让粒子重新回到内部去。再加一个小概率变异机制每代随机挑少量粒子在gbest附近做一次高斯扰动防止整个群体陷在局部极值里出不来。变异概率我一般设0.05~0.1太小没作用太大又把收敛过程搅乱了。2.3 改进效果怎么验证不能光看不跑算法改没改好不能靠感觉必须用对比实验说话。我跑完整项目时做了一组标准PSO和IPSO的对比同一模型、同一批数据、同样迭代次数、各独立运行20次统计最优目标函数值的均值和方差。实测下来IPSO的平均目标函数值更低方差也更小。更直观的是收敛曲线标准PSO大概在迭代到30代左右就卡住了后面几代几乎是一条横线IPSO在迭代后期还会出现几次明显的往下跳这就是变异机制在起作用——每次跳变都意味着它找到了新的更优点。3. 光储系统建模与目标函数拆解3.1 光伏、负荷、储能三个模块怎么建模复现论文时建模是头等大事。这一步做不干净后面全是白算。光伏出力和光照强度近似成正比例但温度影响也不能忽略。我用的是简化工程模型P_pv(t) P_rated × (G(t)/G_stc) × [1 k_T×(T_cell(t) - T_stc)]。其中G_stc取1000 W/m²T_stc取25℃k_T是温度系数一般取-0.004~-0.005左右。G(t)用典型日逐时光照曲线就行。代码实现上我建议把G曲线做成CSV文件读入换地区换数据时只需要改文件不用动代码。储能模型的核心是SOC递推SOC(t1) SOC(t) (P_ch(t)×η_ch - P_dis(t)/η_dis)×Δt/E_rated。注意充电效率η_ch和放电效率η_dis一定不能混用一个数字。很多初版代码把这两个效率合在一起算出来的储能损耗会偏差很大。SOC要限制在上下限之间电池容量和最大充放电功率也是硬约束。还有一个容易漏掉的约束一天结束时SOC要回到初始值。如果不加这个约束储能会被当成“免费电池”无限放电目标函数严重失真。建筑负荷直接给24小时逐时曲线即可。通常按季节分成几类典型日比如夏季、冬季、过渡季三类每类对应一个权重天数比如90天、90天、185天。做加权之后全年购电和售电成本就能估算出来不用逐天仿真。这个处理方式是期刊论文的经典做法复现时直接参考就行。3.2 目标函数年度综合成本最小化规划运行综合优化的目标函数我强烈推荐用等年值成本口径。等年值系数也叫资金回收系数crf r×(1r)^N / [(1r)^N - 1]。r是折现率一般取6%或8%N是项目寿命光伏可以取25年、储能取10年。如果光伏和储能寿命不一样工程上可以按加权或者用共同的综合年限做近似论文里通常会说明取值口径。目标函数写成min C_total crf×(C_pv_unit×P_pv_cap C_ess_unit×E_rated C_pcs_unit×P_rated) C_om_year C_buy_year - C_sell_year。C_pv_unit、C_ess_unit、C_pcs_unit 分别是光伏单位造价、储能能量单价、PCS功率单价。C_om_year 是年度运行维护费用。C_buy_year 是全年从电网购电成本典型日逐时电价×买电功率×权重天数求和。C_sell_year 是余电上网收入。如果当地政策有光伏补贴可以在这一项里加。注意这里售电收入是减掉的因为整体是最小化成本。3.3 约束条件与罚函数处理约束我分成三类功率平衡约束、设备边界约束、SOC循环约束。功率平衡是所有时刻必须满足的物理约束P_pv(t) P_bat_dis(t) P_grid_buy(t) P_load(t) P_bat_ch(t) P_grid_sell(t)。每次评估目标函数前先算一遍功率平衡一旦出现不平衡的时间点做标记并计入惩罚。边界约束比较好处理粒子上下界直接在编码范围里限制住。比如光伏容量取[0, 500]kW储能容量取[0, 300]kWh。SOC约束用罚函数违反上下限或者终值不回初值就在目标函数上加一个很大的惩罚项。罚函数系数刚开始可以设小一点比如1e5然后根据收敛效果调整。罚函数系数的调整经验先跑一次不带罚函数的调试运行看看SOC最大偏离到了什么量级。是偏离了10kWh还是0.1kWh然后反推罚系数。让惩罚项在目标函数里占到20%~50%是比较合适的区间。系数太大会把目标函数曲面做出悬崖粒子很难翻越太小又约束不住最后出来的储能容量会虚高。4. Python代码实现从工程结构到核心逻辑4.1 工程目录与模块划分复现这类论文强烈建议把代码拆成模块千万别把所有逻辑堆在一个main.py里。我这次用的结构是四块数据模块、模型模块、优化器模块、主流程模块。数据文件放在单独目录load.csv、pv.csv、electricity_tariff.csv分开存放。模型模块里包含光伏模拟、储能模拟、成本计算三个独立文件。优化器里放标准PSO和改进PSO两份实现。main.py只做串联。这么拆的好处是出错时可以单独测试某个函数。比如发现成本算得不对直接调cost_calculation.py里的函数传入假数据验证不用跑整个工程。跑论文复现项目时,这个习惯能帮你节省大量排错时间。4.2 粒子编码与上下界设置外层粒子只需要3个维度光伏额定容量、储能额定能量、储能额定功率。编码越简单越好别把24小时的运行决策也塞进粒子——那会把搜索维度直接撑爆。运行调度是内层问题外层只负责容量决策。粒子范围直接用真实物理值的连续区间编码。速度只在优化过程中有更新作用它的物理单位不重要不需要刻意归一化。有一个小经验超参数粒子数量、最大迭代次数可以先拍脑袋设置比如种群数40、迭代50次跑通然后再放大到100×100看效果。一开始就做大规模搜索调试成本太高了。4.3 双层迭代的核心逻辑这是整篇代码最关键的部分没有之一。外层IPSO迭代的流程是初始化粒子种群混沌初始化生成3维容量方案。对每个粒子解码得到一组具体的容量配置。调用运行仿真模块得到全年运行成本。运行成本加上初投资等年值、再叠加约束惩罚得到适应度值。内层调度在仿真模块里完成根据该容量方案和典型日负荷、光伏曲线计算最优的储能充放电策略。内层返回最优运行成本外层用它更新pbest和gbest。循环迭代到收敛条件。内层调度怎么解我第一版用的是规则调度加简单启发式优化。规则是白天光伏富余时先给储能充电充满再上网高电价时段优先放电低电价时段尽量买电充电。这套规则跑得快、逻辑清楚能先把整体框架验证通。后面如果想升级可以内层嵌套另一个PSO但计算量会成倍增加。第一版先跑通再考虑精度优化。这里有个容易踩的坑外层和内层的迭代次数都要控制住。如果你的外层是100代、内层又是100代那目标函数要算100×100次每次还包含24个时刻的仿真跑一轮下来可能要到分钟级别。复现时我用的是外层100代、内层直接用规则调度整体计算量非常可控几分钟就能出结果。4.4 几个关键代码片段下面给出的代码是可运行的简化版本重点在体现思路实际工程里可以在这个基础上扩展。混沌初始化import numpy as np def chaos_init(pop_size, dim, lb, ub): pop np.zeros((pop_size, dim)) rnd np.random.rand(dim) for p in range(pop_size): for d in range(dim): # Logistic混沌映射 rnd[d] 4.0 * rnd[d] * (1.0 - rnd[d]) pop[p, d] lb[d] rnd[d] * (ub[d] - lb[d]) return pop非线性惯性权重def non_linear_inertia(iter_idx, max_iter, w_max0.9, w_min0.4): # 凸递减前期w保持高位后期快速下降 return w_min (w_max - w_min) * (1.0 - (iter_idx / max_iter) ** 2)边界反射def boundary_reflect(x, lb, ub): # 注意如果反射后仍在界外用边界值简单兜底 for i in range(len(x)): if x[i] lb[i]: x[i] 2 * lb[i] - x[i] if x[i] ub[i]: x[i] (lb[i] ub[i]) / 2 elif x[i] ub[i]: x[i] 2 * ub[i] - x[i] if x[i] lb[i]: x[i] (lb[i] ub[i]) / 2 return x储能系统简化仿真def battery_sim(P_pv, P_load, e_bat, p_rated, eta_ch, eta_dis, soc_init0.2, soc_min0.1, soc_max0.9): soc soc_init buy np.zeros(24) sell np.zeros(24) for t in range(24): net P_pv[t] - P_load[t] if net 0: p_ch min(net, p_rated, (soc_max - soc) * e_bat) soc p_ch * eta_ch / e_bat # 实际充电功率 sell[t] max(0, net - p_ch) else: p_dis min(-net, p_rated, (soc - soc_min) * e_bat / eta_dis) soc - p_dis / eta_dis / e_bat buy[t] max(0, -net - p_dis) return soc, buy, sell注意这个版本的battery_sim是高度简化的示意真实项目里还需要把充放电功率换算、电价加权、权重天数等都耦合进去。我在完整工程里是把仿真函数封装成了一个类参数通过配置字典传入方便切换不同电价和负荷数据。5. 调试实录复现过程中最容易踩的坑5.1 结果一直不收敛怎么定位迭代曲线出现锯齿状多半是罚函数在振荡、惯性权重不合适或者随机分量太大。我的排查顺序是先把变异概率调低看曲线是否变平滑再把惯性权重下界调高让后期搜索更稳定最后检查是不是罚函数系数太大导致目标曲面有悬崖。不收敛的另一种表现是多次运行结果方差很大。这说明初始种群对结果影响太强。混沌初始化直接解决这个问题它让初始种群分布更均匀多次运行的一致性会明显提升。5.2 SOC总回不到初值储能容量直接算成0SOC算不平大概率是终值与初值不一致。解决办法是在目标函数里加一个“末态SOC对初值偏离”的惩罚项。注意这个惩罚项要在每次粒子评估时都计算而不是等到全部迭代完成后再处理。储能容量被优化成0主要有两种原因。第一是电价结构问题峰谷价差太小储能套利收益覆盖不了投资成本此时储能容量优化为0是合理结果第二是储能效率参数写得不对或者充放电功率限制设错了储能根本没法有效运行。排查时先把效率参数改成理想值比如0.95如果容量还是0那大概率是电价问题如果容量变大了那就是效率参数写错了。5.3 复现论文结果时数值对不上怎么办复现EI论文不追求逐位一致。论文里很多参数是没写明白的比如折现率、系统寿命、典型日权重、充放电效率这些参数你只要在论文合理假设范围内取就是有效复现。我的建议是建一个参数来源对照表把每个关键参数的取值、出处、是论文给的还是自己假设的全部列清楚放进项目的README里。只要核心结论的趋势能复现——比如“综合优化比顺序优化节省成本X%”这个相对值基本对得上——就算复现成功。绝对值差异是正常的不用死磕。6. 一点个人经验按我的经验做这类课题最重要的不是算法越花哨越好而是把模型的物理意义想清楚、双层逻辑搭对再回头优化算法。我见过太多人一上来就改PSO的各种系数结果模型本身是错的改再好的算法也没有意义。对新手的建议是先把“规则调度标准PSO”跑通拿到一套基线结果再逐步加改进。对进阶读者可以盯着双层嵌套优化方向做企业级扩展比如计入光伏出力的不确定性、引入分时电价下的需求响应甚至把储能寿命衰减模型加进去。这个课题的延展空间非常大做完这篇复现后面能做的事情多得很。最后再分享一个小技巧。不管代码写得多清楚一定要保留一套标准PSO作为对比基线。原因很简单论文需要对比实验项目汇报需要数据支撑你自己排查问题也需要知道改进到底有没有用。留着基线你随时能说清楚每一行代码、每一个改进点的价值。
返回列表