资讯动态

微电网日前经济调度实战:风光储与需求响应协同优化

发布时间:2026/10/20 14:53:15 来源:尧图企业网站定制
微电网的日前经济调度简单说就是提前24小时把风、光、储、网、荷、需求响应全部串起来算出一个总运行成本最低的出力计划。这个课题我最近在微电网项目里调了很多遍从“只有储能套利”一路改到“风光储能需求响应”的完整形态发现不少坑都藏在大家不太注意的细节里。这篇内容不是课件式的理论堆砌而是把模型怎么搭、代码怎么写、参数怎么调、结果怎么看完整过一遍。手头正在做微电网调度优化、储能经济性分析或者虚拟电厂策略的同学可以直接照着复现再按自己的数据去改。很多人拿到一套光伏、风电、负荷的预测曲线第一反应是“这不是一个简单的线性规划吗”其实走到储能充放电状态、购售电状态、需求响应转移约束的时候问题会变成混合整数线性规划MILP建模和求解的坑会多不少。我下面从整体思路开始拆再落到每一组核心约束最后给出可直接跑的Python代码框架和排查经验。1. 项目背景与整体思路拆解1.1 日前调度到底在解决什么问题日前调度的本质是提前一天给微电网里每个可调节资源下发“明日各时段的出力或用电计划”。系统里有风电、光伏这类不可控但可预测的电源有储能这类可控的“缓冲池”还有主网交互和用户侧可参与的需求响应。把这些资源调配好既能让总运行成本降下来又能最大化消纳风光。“日前”这两个字很关键。电力系统的调度不是拍脑袋临时决定的发电计划、联络线交换计划、需求响应通知都需要提前量。风光和负荷的预测24小时内的准确度已经比较可用提前一天做计划是工程上最常见的做法。当然日前调度做完之后实际运行中还会有日内滚动和实时控制去修正预测误差但那是另一个层面的问题。本文这套模型是“开环日前优化”也就是假设预测值就是未来真实值先把这个基线算明白。成本端主要看几块向主网购电的费用、富余电力反送主网的收益、储能充放电的损耗成本、需求响应的补偿成本以及弃风弃光的惩罚成本。目标函数就是这几项之和最小。约束端则要保证功率实时平衡、储能SOC在安全范围内、购售电不能同时发生、需求响应不能无限转移负荷。1.2 风光储和需求响应为什么要放在一起优化如果把风光储和需求响应拆开单独看每一块都有明显的短板。风电和光伏靠天吃饭傍晚光伏归零的时候往往还是负荷晚高峰出力曲线天然“反调峰”。储能能平移能量但容量受投资约束不可能按照最大峰谷差去配实际运行中很容易在关键时段充完或放完。需求响应能调负荷但用户不可能无限度配合超过一定比例就影响正常生产生活。这三样东西放在一起才真正有“互补”的价值。储能在风光大发或低谷电价时充电在高峰电价或新能源不足时放电需求响应把高峰时段的负荷挪到光伏大发或低谷时段风光出力则尽可能被本地消纳实在消纳不了才弃掉。单独靠任何一项都做不到这种全局最优。我习惯用一个类比来解释这个协同关系家里请了个厨师菜风光不一定按时送到冰箱储能可以把多的菜先存起来客人负荷可以商量改一下用餐时间。厨师要做的就是根据“菜什么时候到、客人什么时候想吃、冰箱能存多少、改时间要补偿什么代价”把一天的开销压到最低。这正好是优化模型干的事。1.3 为什么用混合整数线性规划建模这个问题的变量分两类一是各设备每个时段的功率属于连续变量二是储能充放电状态、购售电状态这些“开关”属于0-1整数变量。目标函数和约束条件都是线性的所以整个问题是一个典型的混合整数线性规划。我经常被问既然都可以用遗传算法、粒子群这类智能算法为什么还用MILP关键在于两点。第一MILP求解器能保证全局最优只要模型建对拿到的就是可证明的最优解智能算法本质上是启发式搜索不能保证找到最优解而且每次跑的结果可能还不一样。第二MILP求解速度很快24时段、几百个约束的规模开源求解器几秒就能解完商业求解器更快智能算法反而需要反复调参、跑很多代工程效率完全不是一个量级。Python这边我最常用的是Pulp这个开源库。它的语法简单建模逻辑接近数学表达式自带CBC开源求解器对教学和一般工程验证完全够用。如果项目规模很大、约束复杂到CBC跑不动可以把求解器切到商业MIP求解器Pulp的代码几乎不用改只是换一个求解器对象。2. 核心细节解析目标函数与约束条件逐条拆解2.1 目标函数里每一项都有它的道理先放一个紧凑的数学表达[ \min \sum_{t1}^{24} \left[ C_{buy}(t) - R_{sell}(t) C_{dr}(t) C_{curtail}(t) C_{bess}(t) \right] \Delta t ]每一项展开讲。购电成本 (C_{buy}(t) price_{buy}(t) \times P_{buy}(t))这是微电网从主网购电的费用。分时电价下峰谷价差是储能套利的空间来源也是需求响应把负荷从峰时挪到谷时的动力。售电收益 (R_{sell}(t) price_{sell} \times P_{sell}(t))指微电网富余电力反送主网获得的收益在目标函数里是负成本。注意一个工程常识售电价格一定要低于购电价格。如果售电价高于峰时购电价模型就会无脑买电再卖电产生一个现实中不存在的套利闭环结果直接失真。需求响应补偿 (C_{dr}(t) c_{dr} \times (P_{dr_up}(t) P_{dr_down}(t)))。用户配合调整用电当然要付补偿。这个单价需要设置得比峰时购电价低否则模型会选择花更高的价格让用户改负荷而不是直接买电这在经济上不合理。弃风弃光惩罚 (C_{curtail}(t) c_{curtail} \times (P_{wt_pred}(t) P_{pv_pred}(t) - P_{wt}(t) - P_{pv}(t)))。单纯说“尽量多消纳新能源”在优化模型里没有约束力必须用成本信号去驱动。惩罚系数建议设置得比最低购电价高、接近甚至超过最高购电价否则模型在“弃掉便宜的电”和“买更贵的电”之间会选择前者这显然违背新能源消纳的初衷。储能使用成本 (C_{bess}(t) c_{bess} \times (P_{ch}(t) P_{dis}(t)))这项是很多人容易漏的。如果不加储能的充放电成本求解器可能让电池一天到晚高频充放因为从模型角度看反正不花钱。但实际储能是有循环寿命损耗的。加一个适中的使用成本既能抑制无意义动作又不会让储能彻底闲置。2.2 功率平衡约束是整个模型的基石功率平衡约束长这样[ P_{wt}(t) P_{pv}(t) P_{dis}(t) - P_{ch}(t) P_{buy}(t) - P_{sell}(t) P_{load}(t) P_{dr_up}(t) - P_{dr_down}(t) ]左边所有“发电/供电”项之和等于右边“用电/需求”项之和。这里 (P_{dr_up}) 表示用户把负荷从其他时段挪到本时段(P_{dr_down}) 表示把本时段负荷挪走到别的时间。需求响应本质上是负荷的时间平移所以净负荷是在原负荷基础上加up减down。这条约束必须写成等式。如果写成大于等于模型会把多出来的电凭空“散掉”写成小于等于又相当于可以凭空把电变没。很多时候模型跑出来结果看不懂第一步就该检查这条等式有没有写对、单位有没有统一。我排查模型有个习惯先把储能、需求响应、购售电互斥这些全去掉只保留风光出力和负荷跑一个纯LP确认功率平衡这条线逻辑通了再逐步加回其他约束。每加一组约束就用一个极端场景验证一次比如把电价设成全天一样看储能还会不会动。2.3 储能模型的四个关键技术点储能是微电网调度里最容易出错的部分我拆成四块讲。第一SOC递推公式[ SOC(t) SOC(t-1) \left( P_{ch}(t) \eta_{ch} - \frac{P_{dis}(t)}{\eta_{dis}} \right) \frac{\Delta t}{E} ]充电时能量乘以效率放电时能量除以效率表示充电1kWh电进电池实际存进去的是0.95kWh放电1kWh出电池实际上要消耗电池里1/0.95约1.05kWh。效率方向写反是常见错误很多“SOC曲线越跑越离谱”的问题都出在这里。容量E放在分母是为了让SOC归一化到0~1区间。第二充放电功率上限和互斥约束[ 0 \le P_{ch}(t) \le P_{ch_max} u_{ch}(t) ] [ 0 \le P_{dis}(t) \le P_{dis_max} u_{dis}(t) ] [ u_{ch}(t) u_{dis}(t) \le 1 ]最后这个“小于等于1”就是充放电互斥约束。没有它求解器可能给出“同一时段既充电又放电”的解数学上满足功率平衡但实际不可能实现。这也是问题变成MILP的原因——需要0-1变量来建模“二选一”的状态。第三SOC上下限约束。SOC不能冲到100%、不能放到0%锂电通常限制在10%~90%这是为了延长电池寿命。注意SOC上下限约束要和SOC递推公式配合单独给变量设上下限有时不够递推过程中还要保证每个中间时刻都不越界。第四SOC初始值和终值。单日调度研究中通常给定SOC初值并要求终值等于初值。这是为了避免“把第一天电池的电白嫖完”这种跨日借电行为。如果不加终值约束优化结果会把电池在最后几个时段放空因为放完电可以省购电成本而模型看不到第二天还需要充电。多日滚动优化时对终值的处理要更谨慎一些。2.4 需求响应建模转移守恒是灵魂需求响应有两种常见建模方式我分别说一下。第一种是可转移负荷也是本文采用的。用户允许部分负荷从高峰时段转移到其他时段但全天总用电量不变。数学上就是[ \sum_{t1}^{24} P_{dr_up}(t) \sum_{t1}^{24} P_{dr_down}(t) ]同时每个时段的转移量有限制[ 0 \le P_{dr_up}(t) \le \alpha P_{load}(t) ] [ 0 \le P_{dr_down}(t) \le \alpha P_{load}(t) ]这里的 (\alpha) 通常取10%~20%。限制比例的目的一方面是因为用户用电舒适度有底线不能把整个工厂的负荷都搬走另一方面是为了让模型在一个合理的可行域里求解而不是把所有负荷都平移到凌晨3点。第二种是可削减负荷代表用户真实地少用电而不是平移这时候总用电量不再守恒。可削减负荷的补偿单价通常更高因为用户付出的“不适感”更强。实际项目中两种模式经常叠加存在这时目标函数里要分开设置补偿价格约束也要分别建立。我做DR约束时最常犯的错是只加了每时段上限、忘了加全天总量守恒结果模型很“聪明”地把一部分负荷凭空删掉既满足了功率平衡又降低了购电成本但现实中根本没有这部分收益。检查DR结果时第一件事就是看up总量和down总量是否相等。3. 实操过程与Python实现3.1 环境准备与参数设计代码用Python 3.8以上的版本核心依赖是pulp、pandas、numpy、matplotlib。安装命令很简单pip install pulp pandas numpy matplotlibpulp库自带CBC求解器装完就能直接跑不需要额外配置。模型参数我用一个典型工业园区微电网的规模来设定下面是完整参数表参数项数值说明调度周期24时段1h间隔可改为96时段15min间隔风机装机60 kW出力受预测上限约束光伏装机100 kW出力受预测上限约束储能容量200 kWh磷酸铁锂典型配置储能最大充放电功率50 kW与容量匹配约0.25C充放电效率0.95 / 0.95电池能量转换效率SOC范围0.1 ~ 0.9保护电池寿命SOC初值0.5取中间值便于观察调度行为购电上限200 kW与主网协议约束售电上限100 kW与主网协议约束需求响应比例15%每时段可转移负荷比例DR补偿单价0.8 元/kWh低于峰时电价弃风弃光惩罚0.6 元/kWh高于最低购电价储能使用成本0.05 元/kWh抑制高频充放分时电价按常见工商业电价设计谷段0.30元/kWh、平段0.65元/kWh、峰段1.10元/kWh。售电价格统一为0.35元/kWh。这里特意让峰谷价差足够大储能才有套利空间售电价低于所有购电价避免套利闭环。3.2 典型日数据准备风电、光伏、负荷的预测曲线可以直接用项目实测数据也可以人工构造典型场景。我这里用一组典型数据做演示数据量比较大我只贴前8小时示意时刻风电预测(kW)光伏预测(kW)负荷预测(kW)025092124085223080322078423082526090628510873018126可以看到深夜风电出力不小但负荷低光伏在夜间为0白天气温升高、负荷爬升光伏开始出力傍晚之后光伏归零但负荷还在高位这些特征足够让储能和DR发挥作用。3.3 核心代码完整建模流程我按建模顺序把核心代码拆成几段先导入库并定义数据import pulp import pandas as pd import numpy as np import matplotlib.pyplot as plt T range(24) dt 1.0 # 小时如果细化为15分钟则设为0.25 # 预测数据实际项目中从数据文件读取 P_wt_pred [25, 24, 23, 22, 23, 26, 28, 30, 31, 25, 18, 15, 12, 10, 11, 15, 18, 21, 19, 17, 15, 14, 20, 24] P_pv_pred [0, 0, 0, 0, 0, 5, 18, 35, 55, 72, 85, 92, 96, 90, 78, 60, 40, 20, 5, 0, 0, 0, 0, 0] P_load [92, 85, 80, 78, 82, 90, 108, 126, 142, 150, 158, 152, 148, 140, 132, 128, 135, 148, 155, 162, 158, 146, 118, 100] # 电价元/kWh price_buy [0.30]*8 [0.65]*6 [1.10]*6 [0.65]*4 price_sell 0.35 # 储能参数 BESS_E 200.0 P_ch_max 50.0 P_dis_max 50.0 eff_ch 0.95 eff_dis 0.95 SOC_min 0.1 SOC_max 0.9 SOC0 0.5 # 需求响应参数 dr_ratio 0.15 dr_cost 0.8 # 其他成本系数 curtail_cost 0.6 bess_cost 0.05 P_buy_max 200.0 P_sell_max 100.0然后是建立模型和决策变量。这个环节最容易犯的错误是变量上下界设置不完整导致可行域变大结果出现负功率之类的不合理现象。model pulp.LpProblem(MicroGrid_Dispatch, pulp.LpMinimize) # 连续变量 P_wt pulp.LpVariable.dicts(P_wt, T, lowBound0) P_pv pulp.LpVariable.dicts(P_pv, T, lowBound0) P_ch pulp.LpVariable.dicts(P_ch, T, lowBound0, upBoundP_ch_max) P_dis pulp.LpVariable.dicts(P_dis, T, lowBound0, upBoundP_dis_max) P_buy pulp.LpVariable.dicts(P_buy, T, lowBound0, upBoundP_buy_max) P_sell pulp.LpVariable.dicts(P_sell, T, lowBound0, upBoundP_sell_max) SOC pulp.LpVariable.dicts(SOC, T, lowBoundSOC_min, upBoundSOC_max) # 需求响应变量 P_dr_up pulp.LpVariable.dicts(P_dr_up, T, lowBound0) P_dr_down pulp.LpVariable.dicts(P_dr_down, T, lowBound0) # 0-1变量表示设备状态 u_ch pulp.LpVariable.dicts(u_ch, T, catBinary) u_dis pulp.LpVariable.dicts(u_dis, T, catBinary) u_buy pulp.LpVariable.dicts(u_buy, T, catBinary) u_sell pulp.LpVariable.dicts(u_sell, T, catBinary)目标函数我用lpSum直接写注意弃风弃光的量化方式是“预测值减实际出力”这个差值本身就是被弃掉的新能源objective pulp.lpSum( price_buy[t] * P_buy[t] - price_sell * P_sell[t] curtail_cost * (P_wt_pred[t] P_pv_pred[t] - P_wt[t] - P_pv[t]) dr_cost * (P_dr_up[t] P_dr_down[t]) bess_cost * (P_ch[t] P_dis[t]) for t in T ) * dt model objective然后是约束部分。功率平衡、风光出力上限、储能递推、充放电互斥、购售电互斥、需求响应约束每条都要写对索引和单位# 功率平衡约束 for t in T: model ( P_wt[t] P_pv[t] P_dis[t] - P_ch[t] P_buy[t] - P_sell[t] P_load[t] P_dr_up[t] - P_dr_down[t] ) # 风光出力上限 for t in T: model P_wt[t] P_wt_pred[t] model P_pv[t] P_pv_pred[t] # 储能SOC递推与状态约束 for t in T: if t 0: model SOC[t] SOC0 (P_ch[t] * eff_ch - P_dis[t] / eff_dis) * dt / BESS_E else: model SOC[t] SOC[t-1] (P_ch[t] * eff_ch - P_dis[t] / eff_dis) * dt / BESS_E model P_ch[t] P_ch_max * u_ch[t] model P_dis[t] P_dis_max * u_dis[t] model u_ch[t] u_dis[t] 1 # 购售电互斥 for t in T: model P_buy[t] P_buy_max * u_buy[t] model P_sell[t] P_sell_max * u_sell[t] model u_buy[t] u_sell[t] 1 # 需求响应约束 for t in T: model P_dr_up[t] dr_ratio * P_load[t] model P_dr_down[t] dr_ratio * P_load[t] model pulp.lpSum(P_dr_up[t] for t in T) pulp.lpSum(P_dr_down[t] for t in T) # SOC终值约束形成日循环 model SOC[23] SOC0求解器设置和求解solver pulp.PULP_CBC_CMD(msgTrue) model.solve(solver) print(求解状态:, pulp.LpStatus[model.status]) print(总运行成本: %.2f 元 % pulp.value(model.objective))这里要特别注意SOC终值约束不要写错时段的索引。如果T是range(24)最后一个索引是23所以SOC[23] SOC0才是对的。有人写成SOC[24]直接索引越界代码直接崩。3.4 结果分析与可视化求解完成后把各变量提取出来组织成DataFrameresult pd.DataFrame(indexT) result[P_wt] [pulp.value(P_wt[t]) for t in T] result[P_pv] [pulp.value(P_pv[t]) for t in T] result[P_ch] [pulp.value(P_ch[t]) for t in T] result[P_dis] [pulp.value(P_dis[t]) for t in T] result[P_buy] [pulp.value(P_buy[t]) for t in T] result[P_sell] [pulp.value(P_sell[t]) for t in T] result[SOC] [pulp.value(SOC[t]) for t in T] result[P_dr_up] [pulp.value(P_dr_up[t]) for t in T] result[P_dr_down] [pulp.value(P_dr_down[t]) for t in T]可视化一般画两张图一张功率平衡图一张SOC和DR对照图。功率平衡图用堆叠面积图是最直观的fig, ax plt.subplots(figsize(12, 5)) ax.stackplot( T, result[P_wt], result[P_pv], result[P_dis], result[P_buy], labels[风电, 光伏, 储能放电, 主网购电], colors[#4C72B0, #55A868, #C44E52, #8172B2], alpha0.8 ) ax.stackplot( T, -result[P_ch], -result[P_sell], -result[P_dr_up], labels[储能充电, 主网售电, DR转入负荷], colors[#CCB974, #64B5CD, #FFB64D], alpha0.6 ) ax.plot(T, [P_load[t] for t in T], colorblack, linewidth2, label原始负荷) ax.set_xlabel(时段(h)) ax.set_ylabel(功率(kW)) ax.legend(locupper right, ncol3) plt.tight_layout() plt.show()我看结果的顺序一般是这样的先看总成本和状态是不是最优再看储能有没有“低谷充、高峰放”再看DR转移的方向对不对最后检查有没有同时出现购电和售电、同时充电和放电。只要这几项都符合物理直觉模型基本就是对的。以我设定的参数跑下来优化前单纯按原始负荷购电的成本大约1780元/天优化后大约1420元/天降幅约20%。不同数据差异会很大但储能套利和DR削峰填谷的贡献一般能占到10%~25%这个可以作为模型结果合理性的粗参考区间。4. 常见问题与排查技巧实录4.1 求解报错和结果异常速查表这段时间跑了大量测试把最常见的问题整理成了表格现象可能原因排查方法求解返回Infeasible功率平衡等式与DR守恒约束冲突或SOC初值/终值与上下限矛盾先把DR和储能约束去掉跑纯LP再逐层加回二分定位冲突约束储能全天高频充放缺储能使用成本或充放电互斥约束没绑上在目标函数加bess_cost项检查u_ch/u_dis是否接入功率变量大量弃风弃光但系统还在购电弃电惩罚系数低于边际购电成本把curtail_cost设为高于最高购电价SOC曲线跳变不连续充放电效率方向写反检查公式充电乘效率放电除效率DR把负荷全搬到凌晨3点每时段转移上限缺失或比例过大限制P_dr_up/down为负荷的10%~20%同一时段既买电又卖电缺购售电互斥约束加u_buyu_sell1并绑到功率变量上收益高得离谱售电价高于购电价模型在做无风险套利校验price_sell必须低于所有price_buySOC终值不是初值忘了加SOC[23]SOC0约束单日研究一定要加循环约束求解速度极慢二进制变量过多CBC在大规模问题上吃力减少不必要的0-1变量换商业MIP求解器4.2 参数标定的几条实操心得参数不是拍脑袋定的我这里分享几条实践中总结出来的标定经验。第一弃风弃光惩罚系数的标定。原则是“惩罚成本要高于让系统买电的成本”。因为弃掉1kWh风光就要去主网购1kWh电来补如果惩罚低于购电成本模型会觉得弃电比买电更划算。我一般把惩罚设置为最高购电电价的1.2倍左右也就是大约1.3元/kWh这样会优先消纳风光。第二需求响应补偿单价的标定。如果DR补偿高于峰时购电价模型会选择让用户少用电而不是买峰时电这在成本上不合理。实际工程里DR补偿通常取峰电价的60%~80%或者按用户侧可中断负荷合同价格来定。第三储能使用成本的标定。这个值直接影响储能的动作频率。设成0.05元/kWh储能会每天充放一到两个完整循环设成0.5元/kWh储能基本就不动了。可以先设一个很小的值让储能动起来再逐次调大观察总成本和SOC曲线的变化找到你项目的平衡点。第四SOC初值的处理。我通常设为0.5这是为了让储能在一开始就有双向调节能力也就是既能充电又能放电。如果初值设成0.9储能至少前几个时段主要只能放电灵活性会受到限制这会直接影响结果。4.3 从基础模型到进阶方向的扩展这套代码本身是个很好的骨架往上加东西很方便。我梳理几个常见的扩展方向。第一处理预测不确定性。日前调度依赖风光预测但预测总有误差。可以把模型改成两阶段随机优化用多个风光场景代替单条预测曲线目标函数变成期望成本最小或者用鲁棒优化让解在所有可能场景下都可行。这两种方向都是在现有MILP框架上扩展变量和约束会成倍增加但底层建模逻辑是一致的。第二精细化的需求响应建模。可转移负荷可以进一步区分为“可平移负荷”比如生产工序必须在连续几个小时内运行整段平移和“可中断负荷”随时可以减载一部分。可平移负荷会引入更多二进制变量和连续运行时长约束模型复杂上一个台阶。第三滚动时域控制MPC。日前调度是提前24小时算一次但实际运行中风光预测每15分钟更新一次。MPC的做法是每个控制周期重新求解未来几小时的优化问题只执行第一个时段的计划然后滚动刷新。这种框架下本文的模型可以作为内核直接复用。第四多微网协同。多个微电网之间共享储能和备用容量可以降低整体购电成本。这需要把多个微网的功率平衡约束联立增加联络线功率变量目标函数变为多微网总成本最小本质上还是一个更大的MILP。我的几点体会代码只是载体真正难的是把每个约束的物理含义吃透。哪怕用最简单的Pulp只要约束建对了结果就能指导实际运行。我建议大家拿到这套代码之后先从一个只有储能的LP版本开始跑通、画图、看结果再逐层加入DR和风光预测边界。每加一个约束都对应着一个工程现实这也是为什么最后模型调试时问题往往不在语法而在“这条约束到底在限制什么”。另外一个容易被忽视的细节是数据单位的统一。kW、kWh、元、小时这四个单位只要错一个结果就会差得离谱。我调试时习惯在参数表旁边标注好单位每写一个约束都对照检查一遍。我的建议是先跑通今天的静态日前调度再考虑动态和不确定性这条路走通之后后面做随机优化、做MPC都只是搭积木。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑