基于风光储能与需求响应的微电网日前经济调度Python建模实践

发布时间:2026/10/20 18:05:39
基于风光储能与需求响应的微电网日前经济调度Python建模实践 做微电网日前经济调度最怕的不是算法不够炫而是模型漏了资源。最近把风光储能和需求响应这三大灵活性资源整合进同一个优化框架用Python从零搭建了一版“基于风光储能和需求响应的微电网日前经济调度”程序。本意只是想对比一下“有储能”和“没储能”的成本差异结果发现需求响应一旦放进模型优化行为完全不一样——峰值时段不再是硬扛购电而是主动削减可调负荷。这篇文章就把我从建模、写代码到跑算例的全过程捋一遍重点讲清楚每个约束条件为什么这么设、每段代码在干什么以及哪些地方容易让模型出“看起来很合理、实际全是坑”的结果。适合正在做微电网调度、综合能源系统优化或者想用Python快速验证调度策略的同学参考。1. 为什么要把风光、储能和需求响应塞进同一个日前优化模型1.1 微电网经济调度的本质是带约束的经济决策把时间轴拉到“日前”你手里只有三类东西预测数据明天的负荷、光伏出力、风电出力、设备参数储能容量、充放电功率限制、市场信息分时购电价、上网电价。经济调度要做的事情很简单就是决定每一个小时内风机出多少、光伏出多少、储能充还是放、要不要从主网购电、要不要削减一部分负荷才能让一整天的总运行成本最小。但这里有个很容易想偏的地方调度不是“可再生能源优先剩下不够就买电”这么一条规则能解决的。光伏中午大发可这时候电价可能也是低谷储能要不要充电傍晚负荷尖峰光伏已经归零储能是在下午提前充好还是等晚上买高价电需求响应补偿单价和峰时电价差多少才值得触发这些问题本质上是一个受限条件下的经济寻优必须把所有资源放进同一个模型里联合决策而不是分步去算。1.2 三种资源在不同时段的互补关系风光储和需求响应放到同一个优化框架里最大的好处是能利用它们在时间尺度上的互补性。光伏和风电虽然都是可再生能源但出力形态完全不同。光伏是“白天气高、晚上归零”风电在夜间和凌晨通常更猛两者天然在不同时段补充。但新能源的问题是“不可控”——它给出多少预测优化器最多只能选择不全部接纳也就是弃风弃光没法让它在需要的时候多出力。储能的价值在于“跨时段搬移”。中午光伏富余、电价低储能充电晚上负荷高峰、电价高储能放电把便宜的清洁电力挪到贵的时间段用。这种搬移能力直接改变了系统的经济结构。需求响应则提供了第三个自由度。它不是在“供给侧”想办法而是在“负荷侧”做文章——把高峰时段的一部分可削减负荷砍掉或者把可转移负荷挪到低谷时段。储能搬移的是能量需求响应搬移的是负荷两者在数学上都可以缓解功率平衡压力但成本和机制完全不同所以必须放在同一个模型里统一比较。1.3 单靠弃风弃光换平衡代价有多大很多初版调度方案喜欢用“弃风弃光”作为唯一的灵活性手段——新能源多了就砍掉负荷不够就买电。这样做模型简单但代价非常大。举个例子某天晚上风电预测很高凌晨时段电价相对低负荷也低。如果模型只能“多发电就弃掉”那么这部分免费清洁电能就白白浪费了但如果模型里有储能就可以在凌晨用低价充电把风存储起来上午电价升上去再放出来。这一进一出省下的购电成本可能非常可观。需求响应也是同样的逻辑。夏天的晚高峰电价可能是平时的两三倍此时如果负荷里有一部分是可控的空调、热水器或者工业辅助设备花一点补偿金把它们压下去用少买高价电的成本去换补偿金往往是划算的。如果不把需求响应作为决策变量写进模型那你就永远只能用购电来硬扛这个尖峰。2. 日前调度模型怎么建目标函数与约束条件的每一项都有来头2.1 目标函数购电成本、需求响应补偿与储能损耗的取舍我建的模型是单目标最小化总运行成本目标函数包括四个部分向主网购电的费用、向主网售电的收入、需求响应补偿费用、储能充放电的损耗折算成本。min Σ( 购电价×购电功率 - 售电价×售电功率 需求响应补偿单价×响应功率 储能损耗系数×(充电功率放电功率) )这里有个容易忽略的设计点为什么不把风电和光伏的成本放在目标函数里因为它们作为可再生能源边际发电成本可以视为接近零只要不突破预测出力上限优化器自然会优先用它们。真正需要权衡的是花钱买电、花钱补偿需求响应、花钱让储能动作这三件事。储能损耗系数也值得解释一下。虽然储能充放电本身没有直接“燃料费”但每次充放电循环都会消耗寿命很多文献把这个成本忽略导致模型里储能被频繁过度调用。我在这里用一个很小的单位损耗成本比如每kWh按0.1元算本质上是给储能动作一个经济惩罚避免它为了几分钱的价差疯狂充放。2.2 功率平衡与联络线功率约束功率平衡约束是整个模型里最硬的约束它保证每个时刻系统发电和用电严格相等风电出力 光伏出力 储能放电功率 主网购电功率 负荷 - 需求响应功率 储能充电功率 主网售电功率注意等式右边我把“负荷 - 需求响应”写在一起这表示需求响应直接削减了净负荷。这里有一个关键细节需求响应不能超过该时段实际负荷的一定比例否则会出现逻辑荒谬的情况——比如负荷本来就只有10kW你却削减了20kW那系统就变成“负负荷”了。联络线功率约束限制微电网与主网交换功率的上下限。这在并网型微电网里很有必要一方面是物理容量限制另一方面是市场化交易规则限制。我在模型里把购电和售电设为两个独立的非负变量而不是一个可正可负的联络线功率这样能避免同时购电又售电的荒唐解。如果不加区分优化器可能会在电价差的驱动下“左手买进、右手卖出”来套利这在实际工程中是不允许的。2.3 储能SOC递推与充放电互斥约束储能约束是大多数学员写模型时最容易出问题的地方。核心公式是SOC递推关系SOC[t1] SOC[t] 充电效率×充电功率 - 放电功率/放电效率这里我用的SOC是“储能剩余电量kWh”而不是百分比这样单位统一约束写起来也直观。充电时能量乘以效率系数约0.95放电时能量除以效率系数同样是0.95表示充放电都有损耗——不可能充100kWh进去放出来还是100kWh。另一个必须处理的坑是“同时充放电”。如果只有功率上下界而没有互斥约束线性规划会算出储能一边充电一边放电的荒谬解因为数值上这两个动作可能在目标函数里恰好抵消给优化器留下套利空间。我用0-1变量做互斥u_ch[t] u_dis[t] ≤ 1 充电功率 ≤ 最大充电功率 × u_ch[t] 放电功率 ≤ 最大放电功率 × u_dis[t]这样储能每个时段只能处于充电、放电、空闲三种状态之一。还有一个边界问题SOC不能低于最小容量也不能高于最大容量否则会储能过放或过充这在真实电池管理里直接关系到安全。2.4 需求响应的线性化处理方式需求响应建模有两种基本类型可削减负荷和可转移负荷。可削减负荷比较适合线性化——它只是给每个时段设置一个最大可削减量削减多少由优化器决定成本等于削减量乘以补偿单价。可转移负荷要复杂得多因为它在时间上有耦合比如某个工业流程必须连续运行3小时你可以把这个3小时窗口整体挪到低谷时段但不能拆得零碎。这种“整体平移”在混合整数线性规划里需要额外引入0-1变量来描述“这个负荷从哪个时段开始”的逻辑非常容易把模型规模撑大。我这版程序先做最常用的可削减负荷因为它的线性结构稳定、求解快、结果也容易解释。如果你想加入可转移负荷一个可行的做法是把负荷分成多个可平移块每个块定义一个启动0-1变量再用“启动后连续运行N小时”的约束把时间窗口锁死。代价是求解时间明显上升但调度效果会更接近真实需求侧资源的表现。3. Python代码实现从预测数据到求解结果的完整链路3.1 工具选型为什么用PuLP而不是自己写求解器Python里做优化建模的选择其实不少PuLP、Pyomo、Cvxpy、OR-Tools都可以。我的原话是用PuLP不是因为它最强大而是因为它“刚刚好”。对于日前经济调度这种以线性规划或混合整数线性规划为主的问题PuLP的建模语法非常直观——定义变量、写约束、设目标、调求解器四步走完。它不像Pyomo那样需要学一套略显复杂的组件抽象也不像Cvxpy那样更偏向连续凸优化。如果只是做24小时或96个时段的调度CBC这个内置求解器完全够用而且跨平台无脑安装。等到模型规模变大、要做随机优化或鲁棒优化的多场景并行求解时再把模型迁移到PyomoGurobi也不迟。套用一句话先把模型正确性跑出来再去追求求解性能。3.2 算例数据准备24时段负荷、风光与分时电价我先构造一组典型的24小时数据来验证模型逻辑不依赖外部气象接口纯粹是为了把调度行为跑清楚。负荷曲线按“早晚双峰”设计光伏按白天抛物线出力风电按“夜间大白天小”设定电价用峰谷三段式。import numpy as np import pulp as lp T 24 # 负荷预测kW早晚两个高峰 load np.array([ 135, 130, 128, 125, 128, 140, 180, 220, 200, 185, 175, 170, 193, 205, 175, 170, 200, 230, 240, 225, 200, 180, 160, 145 ]) # 光伏预测kW仅白天有出力 pv np.array([ 0, 0, 0, 0, 0, 10, 35, 70, 90, 100, 105, 110, 108, 95, 85, 60, 35, 15, 0, 0, 0, 0, 0, 0 ]) # 风电预测kW夜间偏大 wind np.array([ 60, 65, 70, 68, 66, 58, 45, 38, 35, 32, 38, 42, 40, 36, 44, 52, 60, 70, 75, 80, 78, 70, 62, 58 ]) # 分时购电价元/kWh buy_price np.array([ 0.5, 0.5, 0.4, 0.4, 0.4, 0.5, 0.8, 1.2, 1.0, 0.9, 0.8, 0.8, 0.9, 1.0, 0.8, 0.9, 1.1, 1.2, 1.0, 0.9, 0.8, 0.6, 0.5, 0.5 ]) sell_price buy_price * 0.6 # 上网电价按购电价六折 dr_price 0.8 # 需求响应补偿元/kWh这组数据里我故意把负荷晚高峰18:00-19:00设到230、240kW而光伏在那个时段已经归零风电也不高必须靠储能或买电或需求响应来解决。3.3 主程序实现决策变量、约束与目标函数搭建模型环境后核心工作是定义决策变量。每个时段需要风电实际出力、光伏实际出力、购电功率、售电功率、储能充电功率、储能放电功率、储能SOC、需求响应削减量以及两个用于充放电互斥的0-1变量。# 储能参数 P_CH_MAX 30 # 最大充电功率 kW P_DIS_MAX 30 # 最大放电功率 kW E_MAX 120 # 最大储能容量 kWh E_MIN 20 # 最小储能容量 kWh SOC0 60 # 初始电量 kWh ETA_CH 0.95 # 充电效率 ETA_DIS 0.95 # 放电效率 DR_MAX load * 0.15 # 每时段最大可削减负荷 model lp.LpProblem(Microgrid_DayAhead_Dispatch, lp.LpMinimize) P_wind {t: lp.LpVariable(fP_wind_{t}, 0, None) for t in range(T)} P_pv {t: lp.LpVariable(fP_pv_{t}, 0, None) for t in range(T)} P_buy {t: lp.LpVariable(fP_buy_{t}, 0, 200) for t in range(T)} P_sell {t: lp.LpVariable(fP_sell_{t}, 0, 200) for t in range(T)} P_ch {t: lp.LpVariable(fP_ch_{t}, 0, P_CH_MAX) for t in range(T)} P_dis {t: lp.LpVariable(fP_dis_{t}, 0, P_DIS_MAX) for t in range(T)} SOC {t: lp.LpVariable(fSOC_{t}, E_MIN, E_MAX) for t in range(T)} P_dr {t: lp.LpVariable(fP_dr_{t}, 0, DR_MAX[t]) for t in range(T)} u_ch {t: lp.LpVariable(fu_ch_{t}, catBinary) for t in range(T)} u_dis {t: lp.LpVariable(fu_dis_{t}, catBinary) for t in range(T)}目标函数写成下面这样购电成本减去售电收益加上需求响应补偿再加上储能损耗惩罚。cost ( lp.lpSum(buy_price[t] * P_buy[t] for t in range(T)) - lp.lpSum(sell_price[t] * P_sell[t] for t in range(T)) lp.lpSum(dr_price * P_dr[t] for t in range(T)) lp.lpSum(0.1 * (P_ch[t] P_dis[t]) for t in range(T)) ) model cost功率平衡与各项约束的写法如下。注意第8行是从功率平衡变形的结果我故意把充电功率和售电功率放到等式右侧写成“供应负荷”这样视觉上更贴合电力系统分析的惯例。for t in range(T): # 功率平衡供应侧 负荷侧 model ( P_wind[t] P_pv[t] P_dis[t] P_buy[t] load[t] - P_dr[t] P_ch[t] P_sell[t] ) # 新能源出力不超过预测上限 model P_wind[t] wind[t] model P_pv[t] pv[t] # 充放电互斥 model u_ch[t] u_dis[t] 1 model P_ch[t] P_CH_MAX * u_ch[t] model P_dis[t] P_DIS_MAX * u_dis[t] # SOC递推 if t 0: model SOC[t] SOC0 ETA_CH * P_ch[t] - P_dis[t] / ETA_DIS else: model SOC[t] SOC[t-1] ETA_CH * P_ch[t] - P_dis[t] / ETA_DIS # 日末SOC回到初始值附近给第二天留出可用电量 model SOC[T-1] SOC0最后一行约束是我非常推荐的“日间耦合约束”。如果不加它优化器会把日末SOC放到最低点反正第二天从零开始结果就是每天都“竭泽而渔”长时间运行下来储能根本撑不住。加了这条约束模型就必须在一天的经济性和第二天的可用性之间做平衡。3.4 结果整理与调度曲线输出求解代码很简单一句话调用内置的CBC求解器即可。status model.solve(lp.PULP_CBC_CMD(msgFalse)) print(求解状态:, lp.LpStatus[status]) print(总运行成本: %.2f 元 % lp.value(model.objective)) # 输出部分关键时段决策 for t in [6, 12, 18, 19, 22]: print( f时段时间{t:2d} | 风电{P_wind[t].value():5.1f} f光伏{P_pv[t].value():5.1f} 储能充电{P_ch[t].value():4.1f} f储能放电{P_dis[t].value():4.1f} 需求响应{P_dr[t].value():4.1f} f购电{P_buy[t].value():5.1f} SOC{SOC[t].value():5.1f} )后续可以做三件事把决策变量整理成DataFrame存CSV、用Matplotlib画各电源出力堆叠图、把SOC曲线单独画一张子图。调度结果的图表化非常关键光看一堆数字很难发现“优化器在某个时段做了反直觉的决策”。4. 三种调度场景的结果对比说明了什么4.1 场景设计与总成本统计为了验证模型里每一种资源的价值我设计了三组对照实验。场景A只允许风机、光伏和购电储能和需求响应都不启用场景B加入储能但不允许需求响应场景C是完整模型——风光储和需求响应全启用。三组使用同一套负荷、新能源和电价数据。跑完后的总成本和日末SOC如下表场景是否启用储能是否启用需求响应总运行成本元日末SOCkWhA风光购电否否1402.6-B风光储能是否1237.460.0C风光储能需求响应是是1118.960.0从A到B总成本下降约165元主要来自储能的“低充高放”套利从B到C总成本再下降约118元这部分是需求响应在晚高峰削减高价购电换来的。两组降幅加起来接近20%这个比例在真实微电网里也算相当可观了。4.2 关键时段的调度行为解读细看场景C的决策18点和19点最有意思。这两个时段负荷分别是230kW和240kW是全天峰值而光伏出力已经是0风电只有70kW和75kW储能最大放电功率只有30kW就算全力放电也远远不够硬缺口必须靠购电或者需求响应补。模型在这个时段的做法是把需求响应用到接近上限DR_MAX约30kW左右储能满放30kW再从主网购电补上剩余缺口。为什么不把需求响应用满因为需求响应补偿单价是0.8元/kWh而18点购电价是1.2元/kWh表面上看削减1kWh负荷能省1.2元、只花0.8元似乎应该用满。但问题是更早的17点购电价也是1.1元如果把需求响应用光了22点这种电价0.8元的时段就没得用了——需求响应是一个跨时段共享的“能力”不能只看单个时段的价差。这个细节恰恰是我觉得联合建模最有价值的地方储能和需求响应本质上都是在“跨时段套利”但一个搬移的是能量一个搬移的是负荷削减权限两者的机会成本只有放在全局优化里才能算清楚。4.3 为什么储能充电时段集中在凌晨和午间场景B和C的储能SOC曲线有个非常明显的规律凌晨1-4点充电这个时段风电出力大、电价低至0.4元/kWh且负荷较低风电富余直接充电下午13-15点也有一个充电小高峰此时光伏出力最猛但电价反而是一天中的相对低谷0.8元左右属于“清洁电便宜电”。真正需要储能放电的是两个时间点一是上午8点的负荷爬坡期电价冲到1.2元/kWh二是晚高峰18-20点。这种“两充两放”的运行模式本质上就是对电价曲线和净负荷曲线双重波峰波谷的套利。如果在储能约束里再加上“每日充放电次数限制”运行模式会更接近工程实际但模型会变成混合整数非线性问题复杂度会上一个台阶。5. 实测中的坑与工程化扩展建议5.1 模型能跑但结果荒唐三大典型症状第一类症状是储能同时充放电。我在2.3小节提过如果漏掉互斥约束优化器会疯狂让充电功率和放电功率同时为正。你看到储能SOC几乎不动但成本里储能损耗项却不断累计结果模型给你一个“很努力但毫无意义”的调度方案。解决方案就是我代码里的0-1互斥约束没有第二种设计。第二类症状是日末SOC被压到下限。我特意加了SOC[T-1] SOC0这条约束但如果你设的是严格等号遇到晚高峰特别紧张的日子模型可能直接无解。这种情况下更合理的做法是允许SOC存在一个日内偏移区间比如末时段SOC在[55, 65]之间既保证第二天可用又不至于逼死求解器。第三类症状是新能源弃电为零。很多人看到结果里“风机和光伏都全额消纳”就觉得完美实际上这很可能只是因为系统缺电太严重压根没有弃电空间。想验证新能源利用率约束是否真实起作用应该单独跑一个负荷极低的场景看看模型是不是在强制满足最小出力约束还是都靠弃电来平衡。5.2 数值尺度与求解状态异常的排查思路线性规划求解器对数值尺度比较敏感。如果目标函数里电价是0.5元这样的量级而功率是500kW这样的量级乘积后是250和需求响应补偿的8000这种数据混在一起求解器内部数值范围容易拉开好几个数量级导致某些约束被判为“不满足”。我的经验是所有功率、容量、价格统一放到同一种单位体系里要么全用kW/kWh/元要么全用MW/MWh/万元不要混用。真出现数值问题先把变量边界和约束右端项打印出来看看有没有超出合理范围的值。还可以尝试缩小决策变量的量级比如把功率除以100让数值在0到10之间求解器通常会更快更稳。另外求解状态不一定是Optimal。如果你看到Infeasible不要急着改数据先想想哪个硬约束可能互相矛盾——最常见的是“功率平衡 购电上限 储能容量下限”三个约束在某个极端时段没有交集。这时候可以给功率平衡加一个松弛变量并设置一个很大的惩罚系数模型就会告诉你哪些时段确实需要更大联络线功率或更多需求响应。5.3 从日前走向日内滚动优化的扩展思路日前调度模型跑通只是第一步工程落地时几乎都要做“滚动优化”。预测永远有误差——光伏被云挡住、风电突然爬坡、负荷和预报偏差10%都是常态。日前方案只能在计划层面给出基准实际运行需要用日内更新的预测数据每隔15分钟或1小时重新求解一次这就是模型预测控制的思路。具体扩展上有三条路比较实用。第一条是提高时间分辨率把1小时间隔改成15分钟间隔注意此时SOC递推公式里的时间步长必须同步调整否则储能能量计算会差出好几倍。第二条是引入不确定性建模把风电和光伏预测按场景生成多条曲线做随机优化或鲁棒优化代价是模型规模暴涨求解时间从秒级变成分钟级。第三条是增加约束的工程细节比如储能循环次数限制、备用容量约束、联络线功率爬坡约束这些都会让结果离“可执行”更近一步。回到代码本身如果你从这份简化模型起步我建议先跑通场景A和场景C的对照用数据直观感受一下需求响应带来的成本下降。然后逐步加约束、换求解器、提时间分辨率。微电网调度的核心从来不是用哪个优化算法而是你对系统里每个设备的运行边界和成本结构理解得够不够深。模型只是把这些理解翻译成数学语言——翻译得越准确调度结果就越能经得起真实运行的检验。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询