风储深度调峰模型:MATLAB+Yalmip建模与求解实践
发布时间:2026/9/30 8:34:56来源:尧图网络
这两年做新能源并网相关项目我最大的感受是系统“看不见”的刚性约束远比想象中多。刚开始接触风储深度调峰模型时我以为只是在MATLAB里把功率平衡公式写对、跑个优化就完事结果真正搭起来才发现火电深度调峰的分段成本、储能SOC的累计误差、Big-M参数选取、甚至求解器的数值稳定性每一个环节都能让你debug到怀疑人生。这篇文章把我实际搭建并反复调过的“风储深度调峰模型”完整拆开来讲从数学建模、MATLAB代码实现到算例结果分析、常见坑位尽量做到可以直接复制参考。适合正在做电力系统调度、新能源消纳、储能优化配置相关课题的同学也适合刚接触Yalmip和优化求解器的朋友。1. 深度调峰这事关键是要先搞清楚火电到底能潜到多深1.1 “深度”到底有多深——从常规最小出力到深度调峰下限火电深度调峰说白了就是让火电机组低于常规最小技术出力运行。常规情况下一台300 MW的机组最小技术出力可能标定在120 MW也就是40%额定负荷左右。但电网低谷时段风大、负荷小如果你不让它继续往下降风电就只能被砍掉。所以就有了“深度调峰”这个概念机组在锅炉不投油、不投等离子助燃的前提下强行压到更低负荷比如30%甚至25%。从工程上讲深度调峰确实伤设备。锅炉水动力稳定性变差汽轮机末级叶片容易产生颤振脱硝系统入口烟温低于催化剂活性温度之后环保指标也会出问题。所以调度侧不能白让你压负荷必须在目标函数里增加深度调峰的补偿或惩罚项否则机组没有动力去冒这个风险。我用的模型里把机组出力分成几个区间机组额定功率常规最小出力深度调峰下限深度调峰区间G1300 MW120 MW80 MW80~120 MWG2200 MW80 MW60 MW60~80 MWG3100 MW50 MW30 MW30~50 MW这三台机组加起来常规最小出力和是250 MW深度调峰后最低能压到170 MW。这个差距就是系统低谷时段的调节空间。1.2 风电为什么要和储能绑在一起风电出力和负荷天然是错位的。大半夜大家睡觉负荷一路掉到300 MW出头但风电偏偏在半夜经常满发。这时候火电已经压到技术下限风电再多就只能弃。储能的定位就是“搬家公司”低谷时段把多余的风电充进来高峰时段再放出去。这样一来火电可以尽量维持在经济运行区间风电的消纳空间也打开了。风储深度调峰模型的核心就是把“火电深度调峰成本”和“储能充电/放电/弃风惩罚”放在同一个优化问题里让他们互相博弈出一个总成本最小的方案。储能作为快速调节资源响应速度和爬坡能力都远优于火电。深度调峰模型里加入储能之后等于给整个系统加了一个“缓冲垫”。低谷时段火电不用压得那么狠高峰时段也不用频繁启停备用机组整个系统的运行压力都会小很多。1.3 模型边界我做的不是机组组合而是调度层优化刚开始做的时候容易搞混一件事深度调峰模型和机组组合、经济调度到底什么关系机组组合是提前一天决定哪些机组开、哪些机组关属于小时级甚至跨日级的决策经济调度更偏向实时在机组启停状态已经确定的情况下分配各机组出力。我搭的这个模型可以理解为两者的结合——时间尺度是日前24小时、分辨率为1小时同时考虑了机组启停状态和出力分配也额外包括了深度调峰区间约束和储能SOC约束。这个尺度对大多数研究场景都够用了而且数据好找、可视化方便特别适合用来做方案对比。2. 数学模型拆解目标、状态变量与约束2.1 目标函数总运行成本最小化我最终用的目标函数是这样的[ \min \sum_{t1}^{T} \left[ \sum_{i1}^{NG} (F_i(P_{i,t}) SU_{i,t}) \lambda_{depth} \cdot D_{i,t} \lambda_{cur} \cdot P_{cur,t} \right] ]其中(F_i(P_{i,t})) 是机组i在t时段的燃料成本我这里简化为二次函数(SU_{i,t}) 是机组启动成本只在机组从停机转运行时才产生(D_{i,t}) 是深度调峰惩罚反映机组进入深度调峰区间的额外损耗(P_{cur,t}) 是弃风功率(\lambda_{cur}) 是弃风惩罚系数。储能本身的充电/放电成本我放在约束里处理储能SOC的“折旧”很小如果单位时间尺度不长可以先忽略。但如果做长期运行策略建议在目标函数里加一个储能循环寿命损耗项否则模型会倾向于让储能频繁充放实际现场根本受不了。2.2 火电深度调峰成本的分段处理火电燃料成本本身是非线性的深度调峰区间还得另算附加成本。我的做法是把机组出力分段每一段对应一个线性化成本常规区间出力≥常规最小出力成本按电厂给出的煤耗曲线拟合深度调峰区间出力低于常规最小出力但高于深度调峰下限除了燃料成本外额外加上一个与压负荷深度成线性关系的惩罚项(\alpha_i (P_{i,min} - P_{i,t}))。这样处理不是最精细的但求解效率很高而且工程上很好解释。如果你拿到的是汽轮机热耗试验数据完全可以用更精细的分段线性函数替代模型结构不用大改。2.3 储能系统约束储能在模型里需要约束五件事充电功率上限、放电功率上限、SOC上下限、SOC递推关系、始末SOC平衡。SOC递推公式[ SOC_{t1} SOC_t \eta_c \cdot P_{ch,t} - \frac{P_{dis,t}}{\eta_d} ]这里有两个效率参数(\eta_c) 充电效率、(\eta_d) 放电效率。注意这两个参数不能约掉否则无论怎么充放能量都不守恒。我在算例里设充电效率0.95、放电效率0.95电池标称容量100 MWh。更重要的是始末SOC平衡约束[ SOC_T SOC_0 ]如果不加这条优化结果一定会把电池在最后一个时段放空因为这对节省目标函数来说是“免费的午餐”但实际调度完全不能接受。2.4 系统约束与简化假设功率平衡[ \sum_i P_{i,t} P_{w,t} P_{dis,t} - P_{ch,t} P_{load,t} ]这就是最简单的无网损平衡方程在区域级光伏、风储建模里先忽略网损是标准做法后续要加潮流可以把(P_{load})换成节点负荷加支路潮流。机组出力约束、爬坡约束、最小开停机时间约束这些是机组组合里的常规配置。风电侧我只约束了[ 0 \leq P_{w,t} \leq P_{w,max,t} ]即风电并网功率不能超过预测可用出力。储能充电功率也不能超过弃风电量不对——实际上储能想要充电可以充火电也可以充风电但在低谷时段储能的充电功率主要来自风电这个关系不用刻意约束功率平衡方程会自动算出来。线路潮流我没有放进模型。原因是这个模型关注的是系统层面的调峰能力不是阻塞问题。如果后续要研究储能在节点位置的差异化作用那需要扩展成DC潮流或多节点经济调度模型。3. MATLAB实现从零开始搭一个能跑的模型3.1 工具箱、求解器和代码结构我推荐用Yalmip工具箱做建模求解器用Gurobi或CPLEX。Yalmip的好处是把符号约束写得跟数学公式几乎一一对应非常省时间而且它对线性化、指示变量、Big-M等处理都很成熟。如果电脑没有Gurobi或CPLEX临时也能用MATLAB自带的intlinprog顶一下但规模一大就知道差距了。优化求解器对大规模混合整数规划问题的分支效率差距极大同样的模型CPLEX可能几十秒解完intlinprog可能要挂机一小时。代码目录我习惯这样组织wind_storage_deep_reg/ ├── data/ # 负荷、风电、机组参数 ├── src/ │ ├── load_data.m │ ├── build_model.m │ ├── solve_model.m │ └── plot_result.m ├── results/ └── main.m数据文件和模型文件分开后期换数据不用动代码。3.2 变量声明与核心约束写法主脚本里我这样声明变量T 24; nG 3; % 机组状态 u binvar(nG, T, full); % 火电出力 P sdpvar(nG, T, full); % 风电并网功率和弃风功率 P_w sdpvar(1, T, full); P_cur sdpvar(1, T, full); % 储能功率正值充电、负值放电也可以拆成两个变量 P_bat sdpvar(1, T, full); % SOC SOC sdpvar(1, T, full);储能不用拆充放两个变量也可以但如果需要分别设置功率上限还是拆开比较方便。我这次为了展示拆成了P_ch和P_disP_ch sdpvar(1, T, full); P_dis sdpvar(1, T, full);功率平衡约束Constraints []; for t 1:T Constraints [Constraints, sum(P(:,t)) P_w(t) P_dis(t) - P_ch(t) P_load(t)]; end储能SOC约束SOC_0 50; % 初始SOC单位MWh SOC_max 100; SOC_min 10; eta_c 0.95; eta_d 0.95; for t 2:T Constraints [Constraints, SOC(t) SOC(t-1) eta_c*P_ch(t) - P_dis(t)/eta_d]; end Constraints [Constraints, SOC(1) SOC_0 eta_c*P_ch(1) - P_dis(1)/eta_d]; Constraints [Constraints, SOC SOC_min*ones(1,T), SOC SOC_max*ones(1,T)]; Constraints [Constraints, SOC(T) SOC_0]; Constraints [Constraints, P_ch 0, P_ch 50]; Constraints [Constraints, P_dis 0, P_dis 50];这里我把P_ch和P_dis都定义成了非负变量所以目标函数里不写储能成本也没有关系反正同一时段充放不可能同时最优。但如果你在目标函数里加了充放电成本或者想让结果更精确需要再加一个互斥约束P_ch .* P_dis 0不过这个是非线性约束尽量不要用求解器会因为非线性而放弃混合整数效率。实践证明只要目标函数和约束设置合理最优解里自然就不会同时充放。3.3 目标函数写法以火电燃料成本为二次函数为例% 燃料成本系数行向量三台机组 a [0.002; 0.0025; 0.003]; % 元/MW^2 b [20; 25; 22]; % 元/MW c [800; 600; 400]; % 元/h FuelCost 0; for t 1:T FuelCost FuelCost sum(a .* P(:,t).^2 b .* P(:,t) c .* u(:,t)); end深度调峰惩罚项我用了一个近似当机组出力低于常规最小出力时附加深度惩罚。这个用implies或者if-then约束写起来很繁琐我的简化做法是直接在目标函数里加一项P_min [120; 80; 50]; P_deep [80; 60; 30]; kdepth [10; 12; 8]; % 深度调峰惩罚系数元/MW DepthCost 0; for i 1:nG for t 1:T DepthCost DepthCost kdepth(i) * max(0, P_min(i) - P(i,t)); end end注意max不能直接用在Yalmip里做目标函数最好引入辅助变量d_i 0和约束d_i P_min - P_i目标函数里用sum(d_i)。完整写法d sdpvar(nG, T, full); Constraints [Constraints, d 0]; for i 1:nG for t 1:T Constraints [Constraints, d(i,t) P_min(i) - P(i,t)]; end end DepthCost sum(kdepth .* d, all);这个方法虽然不是严格的深度调峰分段成本但特别简单而且能鼓励机组在满足出力平衡的前提下尽量少低于常规最小出力相当于对“浅度深调”做软惩罚。想用严格分段线性成本的话需要引入额外的0-1变量把出力区间切成几段模型会复杂不少。3.4 求解参数设置用Gurobi的话ops sdpsettings(solver, gurobi, verbose, 2, savesolveroutput, 1); optimize(Constraints, TotalCost, ops);verbose设为1或2可以看到分支定界的推进情况。如果模型一直卡住先检查是不是有冗余大M约束或者连续变量很多。提取结果P_opt value(P); P_ch_opt value(P_ch); P_dis_opt value(P_dis); SOC_opt value(SOC); P_w_opt value(P_w);4. 经典算例结果分析与关键参数灵敏度4.1 极端低谷算例负荷320 MW、风电100 MW我设计了一个比较极端的低谷场景凌晨2点负荷只有320 MW风电预测出力却有100 MW。三台机组常规最小出力和250 MW所以火电最低要发250 MW加上风电100 MW总出力350 MW比负荷多了30 MW。如果不加储能、不允许深度调峰这30 MW只能弃掉。如果允许深度调峰火电可以压到220 MW刚好在深度调峰区间内风电全额消纳。但代价是火电得深调30 MW深度调峰成本上升。如果加上50 MW/100 MWh储能系统可以让火电维持在250 MW用储能吃掉30 MW的盈余把电量挪到后面的负荷高峰时段。4.2 场景对比常规调峰 vs 深度调峰 vs 风储深度调峰这里放一个24小时的对比结果我把结果整理成表格方案弃风率运行成本万元/日深度调峰电量MW·h火电深调次数仅常规调峰3.5%52.800深度调峰无储能0.5%55.64206风储深度调峰051.91803风储联合后为什么运行成本反而最低因为储能在低谷时吸收的30 MW电量在高峰时段释放替代了高峰期最贵的边际机组出力所以虽然储能充放电有能量损失但综合账算下来还是赚。深度调峰次数也从6次降到3次这对机组寿命友好得多。4.3 储能容量与弃风率的关系我还做了储能容量从50 MWh逐步加到300 MWh的灵敏度测试。结果很直观储能容量100 MWh以下时每增加50 MWh弃风率明显下降系统成本下降也很快储能容量超过200 MWh后边际效益急速衰减因为低谷时段可搬移的富裕电量就那么多如果只做调峰储能容量并不是越大越好还得考虑充放电功率的上限匹配。这给实际规划带来一个启发储能容量的优化不能只看单场景要结合全年风光出力分布、负荷曲线和CCS曲线综合算。用这个模型加一个场景循环很快就能画出“储能容量-弃风率”曲线对做规划的同学很有用。5. 调试阶段踩过的坑与快速定位方法5.1 约束无解先查Big-M第一次跑模型Gurobi直接告诉我infeasible当时一脸懵。后面我用ops.savesolveroutput 1和check(Constraints)逐步排查才发现问题是机组启动约束里的Big-M参数写得太大。我原来写的是Constraints [Constraints, P(i,t) M * u(i,t)];这里的M取的是10000。理论上没问题但实际上M太大会让线性规划松弛后的可行域变得非常宽松求解器数值稳定性变差有些分支节点上出现“假可行解”反而导致最优解搜索效率极低甚至报错。解决方案是把M改成机组额定功率或者更严格地改成该机组的P_max模型就稳定了。凡是涉及0-1变量和连续变量相乘的约束Big-M一定要选紧一点点但也不能太紧否则会割掉可行解。5.2 二次成本函数带来的求解性能问题我一开始燃料成本直接用二次函数模型里带着P.^2如果误差用Gurobi的QP求解器一般没问题但如果机器上只有intlinprog就麻烦了。intlinprog不支持二次目标只能把二次函数线性化。线性化的标准做法是分段线性近似。把出力范围切成20段每一段用线性函数拟合误差控制在很小范围。这工程上完全够用而且求解速度快很多。比较一下我实测跑的用时二次目标 Gurobi18秒线性化目标 intlinprog46秒线性化目标 Cplex9秒。所以如果你的课题规模不大二次目标直接用求解器没问题。但如果你准备扩展成8760小时全年优化建议还是线性化不然单次求解时间会指数增长。5.3 循环内反复建模导致速度慢Yalmip建模本身是有开销的尤其是如果你在for循环里每一轮都重新声明sdpvar并从头搭建约束T24还行T8760直接卡死。我优化后的做法是只建模一次把相关数据全部定义为参数循环里只改参数然后重新optimize。这个技巧在上百次场景扫描时特别重要。每次optimize之前的Constraints和F可以复用改动量不大求解效率会大幅提升。还有就是尽早把Constraints变量预分配成空数组不要用增量拼接过大的表达式。5.4 SOC初始/结束平衡约束的陷阱我第一次把SOC单位设成了百分比0~100然后初始SOC设成50%结束SOC也设成50%结果发现最优解里储能一开始就在充电后面一直在放电看起来好像“赚”了50%的能量。问题出在约束写法我把SOC单位设成MWh但初始值写成了0.5等于只有0.5 MWh。这里容易错的原因是文献里SOC经常写成百分比而代码里直接用MWh单位混了。建议代码里全部统一成MWh所有SOC相关的参数都乘以标称容量。比如初始SOC 50%对应的是50 MWh。统一用MWh为单位后所有约束的物理意义都非常直观。还要注意SOC递推公式的方向。这里我做过一个非常蠢的错位充电时应该SOC(t1) SOC(t) eta_c * P_ch但反向约束的时候如果前后时段写反储能会从“充电”变成“放电”结果整个调度计划完全扭曲。5.5 负荷和风电数据对齐问题这个坑更多出现在真实数据场景。负荷数据默认是北京时间风电预测数据可能给的是UTC或地方时区有时候差一个时区画图的时候没发现但算出来弃风率差很多。建议数据加载后直接画一张24小时负荷、风电曲线图肉眼检查峰谷位置是否正确。数据源不同步后面模型再精确也没有意义。6. 从单算例到工程应用我的扩展路线6.1 多风电场和多储能站的组合优化单风电场、单储能站只是起步。实际工程里一个区域可能有五六个风电场十几个储能柜分布式和集中式并存。扩展时要考虑的是不同风电场之间出力的相关性以及储能站所在节点对电网断面阻塞的影响。模型需要把单母线功率平衡改成多节点潮流约束同时增加线路传输容量约束这时候Yalmip的优势又出来了——直接定义节点导纳矩阵约束写成矩阵形式也很舒服。6.2 随机优化与滚动时域控制风电预测一定不准这里的不准不是统计学意义上的随机扰动而是大尺度天气过程带来的系统性偏差。所以把确定性模型升级成两阶段随机优化或者场景法是有价值的。我在扩展版里加入了30个风速场景用场景削减去掉明显冗余的出力曲线目标函数改成期望成本最小化。结果比单场景版更稳健缺点是求解时间上去了但换来的是更真实的风险决策思路。如果你打算做实时控制建议用滚动时域优化每15分钟更新一次风电预测重新求解未来4小时的调度计划。这样预测误差带来的影响会被每一轮滚动修正掉。6.3 一点实操体会模型搭好后我最大的体会是风电预测精度和储能SOC初值对结果的影响比模型本身的数学形式大得多。与其纠结成本曲线的精细拟合不如先把预测数据和SOC标定做好。另外我强烈建议初学者拿到一个新数据就跑一版最简单的无储能模型作为基准。别一上来就把深度调峰、储能、启动成本、爬坡约束全堆上去不然遇到问题根本不知道是哪层约束引起的。先把不用储能的模型跑顺再逐步往上加复杂度每个中间版本都保存一个可复现的脚本这条习惯帮我节省了大量排查时间。最后再说一个实用的点如果你是学生党做完模型之后可以顺便用MATLAB的exportgraphics把结果图导出成高清PDF写论文和报告直接就能用。我图里用的配色是parula在低分辨率压缩下依然能看出曲线趋势不会像默认的jet那样一条曲线被切成好几段视觉断层。这些小细节虽然跟模型核心无关但在答辩和论文插图时真的挺加分。
网站建设高端定制企业官网