☰
含可再生能源与储能的区域微电网多阶段鲁棒调度最优运行
2026/10/12 4:50:22 网站建设 项目流程

把“含可再生能源和储能的区域微电网最优运行”和“多阶段鲁棒调度”这两个词放进标题里的论文/课题,这几年我前后见过不少。说实话,这个方向属于典型的“看着简单、做起来一堆坑”的题目:模型写出来容易,能跑出收敛结果、还能解释清楚“鲁棒性到底保到了什么程度”,这才是真正筛选人的地方。

这篇文章把整个模拟项目X的复现过程完整写一遍,从问题拆解、数学模型、Matlab代码结构到C&CG求解细节,再到我踩过的几个典型坑,都展开说。适合正在做微电网优化调度课题的同学,也适合工作里需要搭鲁棒调度框架的工程师。代码思路是全流程可复现的,基于YALMIP二次开发,求解器选CPLEX或Gurobi都行。

1. 先拆需求:为什么选择多阶段鲁棒,而不是确定性优化

1.1 确定性模型的三个致命缺陷

先说说最基础的确定性经济调度模型。目标函数是最小化运行成本,约束是功率平衡、机组出力上下限、储能SOC递推这些,把风光出力当成已知的确定值代入,求解器跑一个单层LP/MILP就完事。这个模型作为教学入门没问题,但拿来套真实微电网,一开始就站不住脚。

第一,风电和光伏的预测误差是客观存在的,确定性模型用一个点预测值代表整个出力曲线,相当于默认“预测完全准确”,这在调度层面是不成立的。第二,储能的最优充放电策略对负荷/新能源波动非常敏感,确定性模型给出的解往往是“贴着约束边界”的,一旦实际出力偏离预测,SOC就可能越限或者功率不平衡。第三,电力系统调度真正关心的是“最坏情况下还安不安全”,确定性模型完全回答不了这个问题。

1.2 随机优化和鲁棒优化的本质差异

有些人会问,为什么不用随机优化?随机优化确实能处理不确定性,它把不确定量建模成已知概率分布的随机变量,然后用场景法或者机会约束去逼近真实期望成本。问题是,你很难获得准确的风光预测误差分布,尤其是小规模区域微电网,历史数据量根本不够支撑一个靠谱的分布拟合。就算拟合出来,场景法的计算规模也相当可观,一个24时段、每个时段50个场景的随机调度,变量和约束数量动不动就是几十万级。

鲁棒优化的思路完全不同:我不需要知道不确定量的概率分布,只需要知道它的变化范围,然后保证在这个范围内不管出现什么值,系统约束都不违约、调度方案都可行。这个思想用一句大白话说就是:做预算的时候按最坏情况准备,最后实际开销只要不超过最坏情况,心里就踏实。对应到微电网里,就是无论风光实际出力怎么波动,只要波动落在预设的“不确定集”内,我就保证功率平衡和储能不越限。

1.3 “多阶段”在这里到底指什么

传统两阶段鲁棒模型是min-max-min结构:第一阶段做机组启停和储能计划之类的“慢决策”,第二阶段等不确定量实现后再做经济调度的“快决策”。多阶段鲁棒更进一步,它不是一次性把24小时的决策全定死,而是把时间轴切分成多个滚动窗口,每个窗口内做一次两阶段鲁棒决策,然后随着时间推移不断滚动更新。

如果你熟悉模型预测控制(MPC),可以把多阶段鲁棒调度理解成一个带鲁棒约束的MPC:每到一个时段,系统根据最新实测数据刷新不确定集,然后重新求解一次带鲁棒约束的优化问题,只执行当前或未来短窗口内的决策,后续时段在下一个滚动周期再调整。这样做的好处是可变的保守度——远处的时段不确定集可以适当放宽,近处的时段收紧,不会为了一个遥远的极端场景牺牲全天的经济性。

我复现的这个模型就是这种结构:24小时整体建模,但求解时采用滚动时域框架,每4小时为一个决策窗口,窗口内用两阶段鲁棒处理风光出力不确定,窗口间用状态递推衔接。这样既保留了鲁棒优化的抗风险能力,又比一次性求解全天模型更贴近实际运行节奏。

2. 数学模型核心拆解

2.1 不确定集的构造:盒式加预算约束

鲁棒优化里最核心的建模选择是不确定集。我用的方法是盒式不确定集加预算约束,这也是工程中最常见、线性化最友好的一种方式。

对于风电出力,设预测值为P_wf(t),实际出力P_w(t)满足:

P_w(t) = P_wf(t) + w(t) * ΔP_w(t)

其中ΔP_w(t)是预测误差的最大偏差,w(t)是归一化不确定变量,取值范围[-1,1]。如果只用盒式约束w(t)∈[-1,1],那所有时段都在最坏偏差处取极值,保守度太高,实际的调度成本会明显偏大。所以引入预算约束:

Σ|w(t)| ≤ Γ

Γ就是不确定预算,它控制在最多多少个时段风光出力可以同时达到最坏偏差。Γ越小越乐观,Γ越大越保守。实际操作中Γ取5到8比较合理,对应24时段中有5到8个小时可以同时出现极端误差。

这里有一个容易踩的坑:w(t)的绝对值在建模时需要用引入辅助变量的方式线性化,如果在YALMIP里直接用abs(w)会被识别成非线性项,虽然YALMIP会自动引入二值变量去处理,但我实测下来模型规模和求解时间都会明显增加。手动引入非负变量u1和u2,让w = u1 - u2且|w| = u1 + u2,整个不确定集就是纯线性的,CPLEX跑起来利索得多。

2.2 目标函数与关键约束的建模细节

目标函数是典型的最小化总运行成本:

min Σ [ C_g * P_g(t) + C_grid * P_buy(t) - C_sell * P_sell(t) + C_bat * (P_ch(t) + P_dis(t)) ]

注意储能本身有个充放电损耗成本项,它的作用不是真的缴费,而是让模型不会出现“无意义的充放循环”——不加这个成本项,求解器经常给出同时充放电的荒谬解,加了之后这种操作会承受惩罚成本,自然被避免。

约束方面,最容易被忽视的是功率平衡约束和储能SOC递推约束之间的耦合。功率平衡写成:

P_g(t) + P_w(t) + P_dis(t) + P_buy(t) = P_load(t) + P_ch(t) + P_sell(t)

储能SOC递推:

E(t+1) = E(t) + η_ch * P_ch(t) * Δt - P_dis(t) / η_dis * Δt

这里的大坑是SOC递推是时间耦合的。在鲁棒框架下,P_w(t)是不确定量,SOC递推一旦牵涉到不确定时段,储能状态本身就变成了不确定变量。多阶段滚动为什么比一次性全天求解更合理,就是因为SOC递推只在自己的滚动窗口内严格成立,窗口边界处的SOC作为状态量传递给下一个窗口,就不会出现“全天最坏场景下SOC越限”这种极端问题。

2.3 储能和电网交互的细节处理

储能建模有几个细节必须注意。第一,充放电不能同时进行,这个约束有两种写法,一种是引入二值变量b(t)加两组大M约束,一种是直接用互补约束P_ch(t) * P_dis(t) = 0(非线性)。能干的做法是第一种,虽然会引入24个二值变量,但这是标准的MILP结构,求解器收敛性有保障。

第二,SOC范围一般设在0.2到0.9之间而不是0到1,这是从电池寿命角度考虑的,严格贴0/1边界会加速电池衰减,实际项目里基本没人这么做。

第三,储能在鲁棒模型里的“身份”值得琢磨——第一阶段决策里面,储能要不要参与?我复现的版本里,储能的SOC轨迹是第一阶段决策量,但实际的充放电功率是第二阶段变量,在不确定量实现后再调整。换句话说,储能计划是“先定趋势,后定细节”,这样既保证了计划的稳定性,又给鲁棒调整留了余地。

电网交互的建模就相对直接,购电功率和售电功率分别作为两个非负变量处理,价格不同,不能同时为正。如果配网允许潮流方向切换,这种双变量建模是标准做法。

3. Matlab代码实现全流程

3.1 代码整体架构说明

我复现的代码分四层:

  • 数据层:load_data.m,负责生成负荷预测曲线、风光出力预测曲线和预测误差范围。
  • 建模层:build_uncertainty.m,构建不确定集约束并返回YALMIP的约束对象;build_mp.m,构建C&CG主问题;build_sp.m,构建子问题。
  • 求解层:run_ccg.m,实现列约束生成算法主循环。
  • 后处理层:plot_results.m,绘制调度曲线和收敛曲线。

多阶段滚动体现在run_ccg的外部循环:每4小时一个窗口,窗口内调用一次C&CG,解出当前窗口的调度方案,更新储能SOC状态量,滚动到下一个窗口。

这套结构的优点是模块间解耦:不确定集想改形式,只动build_uncertainty.m;目标函数想加碳排放成本,只动目标函数构造;主问题的求解策略想变,只动run_ccg.m。后续扩展场景、换算法都方便。

3.2 主问题构建与C&CG迭代实现

C&CG的核心思路是把min-max-min问题拆成一个主问题(MP)和一个子问题(SP),通过迭代不断向主问题添加约束来逼近最优解。

主问题是原始min-max-min问题的松弛版本:把内层min后的问题写成带辅助变量eta的标准MILP,每次子问题求解完会得到一个最坏场景下的具体参数值,主问题就把这个场景参数固定下来,变成一个确定性MILP求解。

核心代码结构如下:

% 主问题构建 Options = sdpsettings('solver', 'cplex', 'verbose', 1); MP_cons = []; MP_obj = ...; % 目标函数基准 % 子问题求解代码 function [LB, UB] = solve_ccg(unc_set, data) LB = -inf; UB = inf; MaxIter = 20; tol = 1e-4; for iter = 1:MaxIter % 求解主问题,得到第一阶段解x和辅助变量eta optimize(MP_cons, MP_obj, Options); x_star = value(x); LB = max(LB, value(eta)); % 固定x_star,求解子问题 % 子问题是max-min结构,通过强对偶转为单层max问题 optimize(SP_cons, SP_obj, Options); UB = min(UB, value(SP_obj_in_original)); % 注意要反代回原问题目标值 if (UB - LB) / abs(LB) < tol break; end % 根据子问题解出的最坏场景参数,向主问题添加新的约束 MP_cons = [MP_cons, add_new_cut(x, sp_scenario)]; end end

3.3 子问题的对偶变换与线性化处理

子问题其实是最需要经验的地方。它的形式是:给定第一阶段决策x,求解max-min问题,即寻找最坏不确定量w使第二阶段的调度成本最大,同时内部对y求最小。

这个双层结构不能直接交给求解器。标准解法是:内层min问题对固定w的线性规划取对偶,把内层min换成对偶问题的max,整个子问题就变成单层max问题。但由于此时w和y的对偶变量都以变量形式存在,目标函数里会出现双线性项(w乘以对偶变量),需要用大M法线性化。

双线性项线性化的核心代码如下:

% 假设有双线性项 w(i) * lambda(j) % 设M为一个足够大的常数,引入辅助变量 z(i,j) MP_cons = [MP_cons, z <= M * (delta(i) + lambda_abs(j))]; MP_cons = [MP_cons, z >= -M * (delta(i) + lambda_abs(j))]; % 具体线性化方式视符号而定

这里的M取值很有讲究。M太小会截断可行域,M太大会导致数值病态、求解器警告数值问题。我一般通过一个预计算脚本先求一下各变量的实际变动范围,把M设为该范围上界的2到5倍,而不是随手写个10000。这个细节决定了C&CG能不能在10到15次迭代内稳定收敛。

3.4 代码中的参数设置要义

参数设置方面,我把关键参数整理如下:

参数取值说明
调度时段数24每时段1小时
滚动窗口长度4小时多阶段滚动周期
不确定预算 Γ624时段中最多6个时段同时处于最坏偏差
风光预测误差上限15%相对预测值的最大偏差
SOC允许范围[0.2, 0.9]电池寿命保护
充放电效率0.95 / 0.95锂离子电池常见值
C&CG最大迭代数20加了收敛判据提前终止
收敛阈值1e-4相对间隙

特别注意:收敛判据不要只看上下界绝对差值,要看相对间隙。因为不同量纲下绝对间隙的意义完全不同,有时候UB-LB=50看起来很小,但如果目标函数值本身只有100,那50的绝对间隙意味着50%的不确定度还没收敛,结果根本不能用。

4. 求解过程中的关键经验

4.1 YALMIP二次建模的取舍

用YALMIP建模最大的问题是求解效率损失。同样的模型,直接用CPLEX的MATLAB接口建模可能比YALMIP快30%到50%。但YALMIP的开发效率高,改约束结构方便,对于课题验证阶段是值得的。

如果模型规模变大了、迭代次数上来之后感觉求解时间不可接受,我的建议是先用YALMIP跑通正确性,再固定模型导出为LP/MILP文件(YALMIP的export功能可以做到),然后手写CPLEX/Gurobi接口加载文件求解。这个两步走策略能兼顾开发效率和运行效率。

4.2 求解器选择与参数调优

CPLEX和Gurobi在我实测里差距不大,CPLEX的MILP收敛稳定性稍微好一点,Gurobi的大规模LP求解更快。这个案例中MILP规模本身不大(二值变量主要是储能充放电状态,数量有限),两个求解器都能胜任。

真正影响求解时间的是C&CG的迭代次数和主问题规模。随着迭代进行,主问题里的cut越加越多,MILP规模线性增长。这个阶段要关注求解器参数设置:

Options.cplex.mip.tolerances.mipgap = 1e-4; % 主问题MIP间隙 Options.cplex.mip.limits.nodes = 1e5; % MIP节点数上限 Options.cplex.timelimit = 300; % 单次求解时间上限

设置时间上限很重要——C&CG循环中如果某个主问题卡住了,整个求解过程都会停滞,时间上限可以保证循环能继续往下走,不会卡死在一次求解里。

4.3 滚动窗口让模型更贴近实际

多阶段滚动相比单次全天求解,还有一个很现实的好处:可以更新预测数据。实际微电网运行中,每过一个小时就能拿到新的风光实测数据,滚动窗口里近期的预测误差范围可以收窄,远期保持较宽,这样调度方案“越近越准、越远越稳”,经济性和鲁棒性之间的权衡比一次性模型合理得多。

我在代码里用一个简单的数据更新机制模拟这个过程:每个滚动窗口开始前,把当前时段的实际出力抽取出来作为已知值,下一时段起才是带不确定性的待测变量。这样SOC的递推链始终是由实测值驱动的,不会累积一整天的预测误差。

5. 常见问题与排查技巧实录

5.1 C&CG怎么收敛那么慢

这个是最常被问的问题。子问题每次给主问题加的cut只有一个场景,如果初始解离最优解太远,迭代路径就会比较曲折。我的经验是给主问题加一个初始可行解,用确定性场景跑一遍得到一组合理的机组出力和储能SOC,作为C&CG的初始解。这样能把迭代次数从二十多次压到十次以内。

另一个原因是子问题求解不精确。子问题里的内层对偶问题求解完成后,要把结果代回原问题验算,确保目标值确实等于对偶问题的最优值。如果对偶间隙存在,C&CG的上下界关系就不成立,收敛曲线会抖动。

5.2 储能SOC越界的尴尬

不管怎么调,SOC还是偶尔会越界?特别是滚动窗口之间的衔接处。排查思路是检查两个窗口的SOC交接是否实现了硬约束:窗口结束时SOC范围要缩窄到交接点附近,下一个窗口开始时的SOC要锁定为上一个窗口的终值。如果这里用的是软约束或者干脆没写,SOC递推链上就会出现跳变窗口。

5.3 大M的选择困境

大M取值影响两个地方:一是双线性项线性化的精度,二是MILP的数值稳定性。M太大,主问题求解时CPLEX会警告数值问题;M太小,线性化后的可行域可能不包含真实最优解。

我的处理办法是先求一次确定性模型,得到所有变量的合理取值范围,然后让M等于范围内最大绝对值的5倍。有一次我把M设成1e6,主问题求解时间直接翻了三倍,还出现MIP gap波动的怪象,调回2000之后一切正常。

5.4 求解器报错“infeasible”的排查路径

如果某个滚动窗口的C&CG迭代中途主问题报不可行,最常见的三个原因:一是前一个窗口传下来的SOC状态和当前窗口机组最小出力的组合无法同时满足;二是风光出力区间在某个时段取叠加上下限后,与机组爬坡约束冲突;三是预算约束写错了方向。

排查顺序建议:先把不确定集退化成确定性模型,如果还是不可行,就是基础约束有问题;如果确定性模型能解而加上不确定集后才不可行,则在子问题返回场景时打印该场景的出力值和约束违约情况,定位到具体的冲突时段。

6. 实操中的体会

这个模型复现下来,最大的体会是:鲁棒优化的难度不在理论而在工程。理论框架教科书上都写得很清楚,min-max-min结构、强对偶、C&CG,知识点就那些。但真正动手写代码的时候,双线性项的线性化怎么做才不伤数值稳定性、大M取多大、滚动窗口之间状态怎么衔接、不确定性预算选多少能在保守度和经济性之间找到平衡,这些问题没有任何教材能给你标准答案,全靠一遍遍调试积累经验。

另外一点想说的是,参数扫描很有必要。我通常会把不确定性预算Γ从2扫到10,画一条“成本-保守度”曲线。你会发现它是一条单调递增的曲线,但增长率会逐渐放缓。选择一个曲率变化明显的转折点作为实际采用的Γ值,这样既不浪费经济性,又具备足够的抗风险能力。这个判断方法比单纯拍脑袋取中间值靠谱得多。

最后,贴上这个项目X的代码时,建议你先把不确定集部分注释掉,用确定性模式对照着跑一遍,能直观感受鲁棒模型和确定性模型的解有什么差别。差别不大,说明不确定集构造得太松;差别过大,说明保守度需要降低。等调出这个手感,鲁棒调度的内涵也就算真正吃透了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询