尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

基于改进粒子群算法的建筑光储系统规划运行综合优化实践

基于改进粒子群算法的建筑光储系统规划运行综合优化实践 做过光储系统优化的人应该都体会过那种两头堵的感觉算法写得再漂亮模型没搭对结果一样不能看模型建得有模有样粒子群一收敛到局部最优又得回头改算法。最近我把一篇EI论文里的建筑集成光储系统规划运行综合优化方法完整复现了一遍用改进粒子群算法PSO在Python里跑通了全流程。这篇文章就把我从问题建模、算法改进到代码实现与调参踩坑的整个过程梳理出来希望能给正在做相关毕业设计、论文复现或者微电网工程规划的朋友提供一点可以直接上手的东西。这个项目处理的问题一句话说清楚为建筑配上屋顶光伏和储能后到底装多少光伏、装多大储能、日常怎么调度才能让全生命周期费用最低传统做法喜欢把“规划”和“运行”分成两步走先定容量再调策略但那样算出来的容量往往不是真最优。这里采用的优化方法是把容量决策和典型日运行策略同时放进一个优化模型里用改进PSO统一求解。代码用Python实现结构很清晰拆开看其实不到几百行就能跑出结果。无论你是想复现论文结果还是想在自己项目里搭一套微电网优化框架这篇内容都适用。1. 建筑集成光储系统的优化问题到底在优化什么1.1 为什么规划与运行要放在一起建模很多人一开始会问光伏容量、储能容量这不是设计院做的活吗运行策略不是能量管理系统做的吗为什么非要放在一个模型里算我刚开始也有这个疑问直到自己用两步走的方法跑了一遍才算明白。规划与运行实际上是一枚硬币的两面储能容量决定了它每天能搬多少电而运行策略决定了这些电是怎么搬的。如果先按年最大负荷和极端日粗选一套容量再去优化运行运行优化只能“戴着镣铐跳舞”。比如光伏装得很大但储能容量配小了午后大发时段只能大量弃光反过来储能容量堆得很高但运行策略不合理可能整天在电价峰时段之前就放完了电完全没起到削峰填谷的作用。这类问题只有把容量变量和运行变量放进同一个优化框架里才会自然形成“多装光伏配合储能多搬电量”或“储能刚好够日循环次数”这种匹配结果。再深一层建筑光储系统的经济性评估依赖于典型日运行模拟。一个容量组合好不好要看它在一年中多个典型日比如夏季、冬季、过渡季的购电成本、储能老化成本和自发自用比例综合下来费用最低才是最优。如果规划阶段不看运行运行阶段改不了容量最后算出的全生命周期费用必然偏保守。所以“规划运行综合优化”这个题目本质是在一次求解里同时回答两个问题装多大、怎么用。在代码实现上我们通常把决策变量分成两层上层是规划变量包括光伏额定功率、储能额定功率、储能额定容量下层是运行变量包括各时段储能充放电功率、与电网的交互功率等。粒子群跑一次要同时给出一套完整的容量配置和对应的典型日调度结果每一组候选解都要经过完整的目标函数计算才能判断好坏。1.2 目标函数与约束条件的数学化拆解这个优化问题最核心的目标函数按论文里最常见的形式是“全生命周期等年值费用最小”。用大白话说就是把光伏设备、储能设备的初始投资折算到每一年再叠加上每年的运维费用、购电费用减去卖电收益得到一个“年化总费用”。谁的年化总费用最低谁就是最优配置和运行方案。等年值折算的公式其实很像是房贷的月供公式。假设一次性投资是P折现率是r寿命是n年那每年的等年值A就是A P * r * (1r)^n / [(1r)^n - 1]在这个项目里我用的折现率是6%光伏寿命20年储能寿命10年。储能寿命短所以要单独按10年折算如果项目建设期考虑20年中间还要考虑一次储能的更换成本。这些参数在论文里都有复现的时候一定要对齐否则最终费用的绝对值完全对不上。目标函数写出来大概是min C_total C_inv_pv C_inv_ess C_om C_grid - C_sell其中C_inv_pv是光伏等年值投资C_inv_ess是储能等年值投资C_om是年运行维护费用C_grid是年购电费用C_sell是余电上网收益。所有这些费用都通过典型日模拟乘以天数累加得到。优化变量包括规划变量和运行变量两类。为了控制粒子群搜索空间的维度我在复现时做了简化光伏容量和储能容量作为优化变量储能充放电策略用“峰谷套利规则”嵌入目标函数计算。也就是说每一组容量变量都会触发一次运行模拟根据当天光伏出力、负荷和分时电价决定储能何时充电、何时放电。这样做的好处是维度低、收敛快同时规划和运行仍然在同一个函数里互相影响算得上“综合优化”。约束条件方面最重要的是功率平衡约束每个时刻光伏出力加储能放电加购电要等于建筑负荷加储能充电。其次是储能SOC动态约束SOC(t1) SOC(t) - P_bat(t) * Δt / Ess_cap这里充电时P_bat为负还要乘上充放电效率。SOC上下限通常设为0.1到0.9不能过充过放。另外还有购电功率上限、储能充放电功率上限、光伏出力上限等。这些约束在PSO里我全部用罚函数处理简单高效后面代码部分会详细讲。2. 标准粒子群算法不够用改进点到底在哪2.1 标准PSO的核心迭代公式与常见病粒子群算法PSO是1995年提出的老牌元启发式算法原理不复杂一群粒子在解空间里飞每个飞的位置对应一组解每次迭代根据个体历史最优位置和全局历史最优位置来更新自己的速度与位置。速度更新公式是每个写优化算法的人闭着眼睛都能默写出来的v wv c1r1*(pbest - x) c2r2(gbest - x)x x vw是惯性权重c1、c2是学习因子r1、r2是[0,1]之间的随机数。参数不多实现也容易但真拿它来解建筑光储规划问题我踩过坑。最典型的问题是早熟收敛。光储优化模型的目标函数非凸、多峰标准PSO很容易在迭代早期就找到一个小谷底然后所有粒子迅速向这个位置靠拢粒子之间的速度越来越小最后基本失去探索能力。我最初用标准PSO跑这个模型往往不到30代就完全收敛而且连续跑十次每次答案都不一样说明收敛到的根本不是全局最优。能源系统的容量配置差10%全生命周期费用可能差几十万这个问题不能忍。另一个问题是约束处理太粗糙。标准PSO本身不带约束概念如果直接把SOC约束、功率平衡约束塞进目标函数罚函数系数没调好算法很容易为了降低罚项而把粒子推向不满足约束的区域。所以改进PSO不仅要改搜索机制还得配合合理的约束修复手段。2.2 我采用的三种改进策略我这次复现采用的改进策略没有上什么花哨的混合算法主要是三个方向自适应惯性权重、混沌映射初始化、高斯扰动变异。这三个改进计算开销很小代码改动也不大但效果非常明显。首先是自适应惯性权重。标准PSO通常把w设成一个固定值比如0.6。问题是迭代前期需要大权重保持全局搜索后期需要小权重精细收敛。我采用线性递减策略w w_max - (w_max - w_min) * t / Tw_max取0.9w_min取0.4t是当前迭代次数T是最大迭代次数。这样算下来前期粒子飞得快搜索范围大后期逐渐放慢围绕最优区域精细搜索。其次是混沌映射初始化。标准PSO用均匀随机初始化粒子分布其实经常扎堆初始种群多样性不够。混沌序列的特点是遍历性好、不会重复非常适合作初始化。我用Logistic映射生成初始位置x(n1) 4 * x(n) * (1 - x(n))这个映射在参数为4时处于完全混沌状态生成的序列能较均匀地覆盖[0,1]空间。把每个维度的初始位置用混沌序列映射到上下界比单纯用np.random.rand要稳得多。最后是高斯扰动变异。在迭代后期gbest很容易陷入局部最优我每次迭代以一定概率比如0.1对gbest加一个高斯扰动gbest_new gbest * (1 Gaussian(0, 0.02))如果扰动后适应度更好就更新gbest否则保留原值。这个操作相当于给算法加了“跳出坑”的机制让它在后期仍然有机会搜索更优区域。我实际跑下来加了扰动之后收敛曲线的尾部不再是完全平的一条直线时常还会出现小幅下降最终适应度比标准PSO低8%到12%。这三个改进单独拿出来都很简单但合在一起正好对症光储规划问题的高维度、多约束、易早熟特点。参数设置方面我固定种群规模为40最大迭代次数300次c1c22.0速度上限设为变量范围的20%。这个配置在这个规模的问题上实测很稳。3. Python代码实现从建模型到跑出结果3.1 环境准备与代码结构Python环境不用搞得很复杂。我用的Python 3.8版本核心依赖只有numpy、pandas和matplotlib三个库。如果你机器上还没装Python建议直接去官网下载3.8以上的安装包安装时勾上“Add to PATH”其他保持默认就行。装好之后在命令行执行pip install numpy pandas matplotlib如果pip下载速度慢可以使用国内镜像源换源的方法就不细写了网上搜一下一大把。另外建议装一个IDE比如PyCharm或者VS Code再配一下Python环境调试粒子群迭代过程会方便很多。代码结构我分成了四个文件data.py存放典型日负荷、光伏单位容量出力曲线、分时电价等输入数据。model.py定义目标函数、约束检查和典型日运行模拟逻辑。pso.py改进粒子群算法的核心类。main.py主流程负责初始化参数、调PSO、输出结果和绘图。如果你只是快速验证也可以把全部代码塞进一个Notebook里跑但分文件的好处是模型和算法解耦后面换算法、换数据都很方便。复现论文时我强烈建议保持这个习惯因为你很可能要反复改目标函数和约束分离开来不会牵一发动全身。3.2 目标函数与约束处理的代码实现目标函数是整个复现的灵魂。我在model.py里写了一个objective函数输入是粒子位置输出是适应度值。粒子位置的前几个维度是规划变量后面如果论文里有运行变量也可以继续扩展。规划变量我设置成三维光伏额定功率pv_cap单位kW、储能额定容量ess_cap单位kWh、储能功率ess_power单位kW。如果论文里还考虑了逆变器功率、光伏安装面积等可以继续增加维度。在实际计算时我会先用等年值公式计算投资成本然后调用一个daily_simulation函数做典型日运行模拟返回购电费用和售电收益。核心的daily_simulation逻辑是def daily_simulation(pv_cap, ess_cap, ess_power, load, pv_pu, price): hours len(load) soc 0.2 # 初始SOC grid_buy [] grid_sell [] for t in range(hours): pv pv_cap * pv_pu[t] load_t load[t] # 先判断光伏是否超发光伏多余优先给储能充电 surplus pv - load_t if surplus 0: charge_power min(surplus, ess_power, (0.9 - soc) * ess_cap / delta_t) soc charge_power * delta_t * charge_eff / ess_cap pv_to_load load_t pv_to_sell surplus - charge_power else: # 缺电时根据电价决定是否放电 discharge_power 0 if price[t] discharge_threshold: discharge_power min(-surplus, ess_power, (soc - 0.1) * ess_cap / delta_t) soc - discharge_power * delta_t / discharge_eff / ess_cap grid_buy_t load_t - pv - discharge_power ...这里要特别小心单位问题。光伏出力模板pv_pu是单位kWp在不同时刻的出力标幺值实际出力是pv_cap乘以标幺值。SOC更新时储能容量单位是kWh充放电功率是kW时间步长delta_t通常取1小时所以电量和功率的换算关系很直接。充放电效率我取了0.95意味着充电1kWh进去实际只能存储0.95kWh放电时还要再乘0.95。SOC初始值我设为0.2同时强制要求一天结束时SOC不低于0.2避免储能被“白嫖”。约束处理方面我用罚函数法。每次计算完运行模拟后检查SOC是否越界、购电功率是否超上限如果有违反就在适应度上加上一个很大的惩罚项。这里的惩罚系数我设为1e6实测效果不错不会让粒子钻空子。如果你发现结果经常违反约束可以把惩罚系数继续调大比如1e8但也不要太大否则会抑制搜索。3.3 核心PSO迭代流程与关键代码pso.py里我写了一个PSO类重点在初始化、速度更新、变异三个方法上。先看初始化。这里用混沌映射生成初始种群避免随机扎堆class PSO: def __init__(self, n_particles, dim, lb, ub, max_iter): self.n_particles n_particles self.dim dim self.lb np.array(lb) self.ub np.array(ub) self.max_iter max_iter self.w 0.9 self.w_min 0.4 self.c1 2.0 self.c2 2.0 self.v_max 0.2 * (self.ub - self.lb) def init_particles(self): # Logistic混沌序列初始化 sequence np.zeros((self.n_particles, self.dim)) for d in range(self.dim): x np.random.rand() for i in range(self.n_particles): x 4.0 * x * (1.0 - x) sequence[i, d] x self.pos self.lb sequence * (self.ub - self.lb) self.vel np.random.uniform(-1, 1, sizeself.pos.shape) * self.v_max速度更新时我先按迭代次数更新惯性权重再计算速度并对速度做限幅防止粒子飞出边界def update_w(self, t): return 0.9 - (0.9 - self.w_min) * t / self.max_iter def update_velocity_position(self, t): w self.update_w(t) r1 np.random.random(self.pos.shape) r2 np.random.random(self.pos.shape) cognitive self.c1 * r1 * (self.pbest - self.pos) social self.c2 * r2 * (self.gbest - self.pos) self.vel w * self.vel cognitive social self.vel np.clip(self.vel, -self.v_max, self.v_max) self.pos self.vel self.pos np.clip(self.pos, self.lb, self.ub)变异逻辑我放在每轮更新gbest之后def mutate_gbest(self): if np.random.rand() 0.1: perturb np.random.normal(1.0, 0.02, sizeself.gbest.shape) new_gbest self.gbest * perturb new_gbest np.clip(new_gbest, self.lb, self.ub) if objective(new_gbest) self.gbest_fitness: self.gbest new_gbest self.gbest_fitness objective(new_gbest)main.py里只需要设置好目标函数和参数然后实例化PSO类循环迭代保存收敛曲线。看到这里你会发现改进PSO并没有多神秘核心就是把初始化、权重更新、变异这三处细节做扎实。4. 结果复现与分析怎么看收敛曲线和优化结果4.1 典型输出结果分析跑完100次实验后我把结果做了对比先看收敛曲线。标准PSO通常在20代到30代就停滞最终适应度稳定在较高水平改进PSO会在前50代快速下降中期偶尔有小幅跳变直到150代以后才趋于稳定。最终改进PSO得到的最优适应度比标准PSO低约10%这个差距反映到实际费用上就是几十万元的全生命周期成本差异。从最优配置看改进PSO倾向于给出光伏容量略小于建筑峰值负荷、储能容量相对偏大的组合。比如我按某办公建筑典型日数据复现时得到的光伏额定功率是165kW储能容量是180kWh储能功率是90kW。当时按传统经验法估算光伏会配到200kW以上储能可能只配80kWh但优化结果说明多出来的那部分光伏在无储能支撑的情况下会被大量弃光不如少装光伏、多配储能提高系统对光伏发电的消纳比例。运行曲线更有意思。画出典型日24小时功率曲线后可以看到储能会尽量在午间光伏大发时充电在傍晚用电高峰放电减少高价电买入量。光伏出力不足的夜间储能SOC逐步下降但始终不低于0.2。购电曲线也不再是平的而是明显呈“晚高峰前买入少、夜间低谷买入多”的特征。这就是峰谷套利与自发自用两种策略在优化结果里的自然体现。因为PSO是随机算法我每次运行都固定了随机种子同时连续跑20次取最优结果。固定种子很重要否则论文复现时你很难解释为什么同一个参数跑两次结果差5%。如果你发的代码里不固定种子审稿人或者老师可能会直接让你补一个多次运行标准差分析所以一开始就把这个习惯养好。4.2 复现过程中的常见问题与排查技巧复现这类论文踩坑几乎是必然的。我把最常遇到的问题整理成表格都是我实际跑出来的经验不是理论推演。常见现象可能的根本原因解决与排查方法迭代不到30次就完全收敛惯性权重固定早熟改用自适应惯性权重从0.9线性降到0.4储能SOC频繁越界罚函数系数太小把惩罚系数调到1e6以上或在模拟中直接截断SOC结果每次运行天差地别随机性太大未固定种子设置np.random.seed(42)多次运行取最优目标函数出现NaN速度更新过大数据单位不统一限制速度上限检查kW/kWh是否混用最优光伏容量为0初始种群多样性不足边界范围太宽用混沌初始化并把变量下界设为一个合理小值购电费用大于预期储能充放电效率没乘对检查SOC更新公式充电要乘效率放电要除效率结果虽然收敛但不满足功率平衡运行模拟逻辑里漏了功率平衡约束在daily_simulation中每个小时都检查一次功率平衡这里特别想说一个坑SOC更新公式里充放电效率的方向。很多人会写SOC(t1) SOC(t) P_bat * Δt / cap然后取充放电效率为0.9。这其实是近似处理但严格来说放电时物理意义是电池放出能量容量下降量要除以放电效率。如果你两个方向都简单乘一个效率SOC可能会越算越多或越算越少最终结果偏差很大。我的做法是充电时soc charge_power * Δt * charge_eff / ess_cap放电时soc - discharge_power * Δt / discharge_eff / ess_capcharge_eff和discharge_eff都设为0.95综合Round-trip效率约90%这个口径和论文里常用的储能效率定义也一致。另外一个容易忽略的是边界条件一天开始时SOC设为0.2一天结束时也必须保证SOC不低于0.2。这其实是一种“日间调度完整性”约束不然算法会为了降低运行成本把储能里面的电放到0第二天再从头充这和实际运行是完全脱节的。关于参数调整我建议不要一上来就疯狂调参。先把标准PSO跑通看收敛曲线卡在哪里再逐步加改进策略。如果标准PSO已经卡在某个局部最优加了自适应权重还解决不了就加混沌初始化如果收敛曲线尾部还有下降空间就加变异。每一步都做对比实验这样你才知道是哪个改进起了作用。最后再分享一个我自己的调参习惯每次改完算法或者改完目标函数我都会把初始种群固定住在同样的起点下对比改进前后的收敛曲线。这样做的原因是PSO的随机性太大如果不固定初始种群你很难判断结果变好是算法改进的功劳还是这一次随机运气好。固定初始种群之后标准PSO和改进PSO的对比就变得非常直观收敛曲线画在一起差距一目了然。这个习惯在复现论文时帮了我大忙也推荐给你试试。
返回列表