含可再生能源与储能的区域微电网多阶段鲁棒调度最优运行

发布时间:2026/10/12 4:50:30
含可再生能源与储能的区域微电网多阶段鲁棒调度最优运行 把“含可再生能源和储能的区域微电网最优运行”和“多阶段鲁棒调度”这两个词放进标题里的论文/课题这几年我前后见过不少。说实话这个方向属于典型的“看着简单、做起来一堆坑”的题目模型写出来容易能跑出收敛结果、还能解释清楚“鲁棒性到底保到了什么程度”这才是真正筛选人的地方。这篇文章把整个模拟项目X的复现过程完整写一遍从问题拆解、数学模型、Matlab代码结构到CCG求解细节再到我踩过的几个典型坑都展开说。适合正在做微电网优化调度课题的同学也适合工作里需要搭鲁棒调度框架的工程师。代码思路是全流程可复现的基于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(t1) 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构建CCG主问题build_sp.m构建子问题。求解层run_ccg.m实现列约束生成算法主循环。后处理层plot_results.m绘制调度曲线和收敛曲线。多阶段滚动体现在run_ccg的外部循环每4小时一个窗口窗口内调用一次CCG解出当前窗口的调度方案更新储能SOC状态量滚动到下一个窗口。这套结构的优点是模块间解耦不确定集想改形式只动build_uncertainty.m目标函数想加碳排放成本只动目标函数构造主问题的求解策略想变只动run_ccg.m。后续扩展场景、换算法都方便。3.2 主问题构建与CCG迭代实现CCG的核心思路是把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 end3.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。这个细节决定了CCG能不能在10到15次迭代内稳定收敛。3.4 代码中的参数设置要义参数设置方面我把关键参数整理如下参数取值说明调度时段数24每时段1小时滚动窗口长度4小时多阶段滚动周期不确定预算 Γ624时段中最多6个时段同时处于最坏偏差风光预测误差上限15%相对预测值的最大偏差SOC允许范围[0.2, 0.9]电池寿命保护充放电效率0.95 / 0.95锂离子电池常见值CCG最大迭代数20加了收敛判据提前终止收敛阈值1e-4相对间隙特别注意收敛判据不要只看上下界绝对差值要看相对间隙。因为不同量纲下绝对间隙的意义完全不同有时候UB-LB50看起来很小但如果目标函数值本身只有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规模本身不大二值变量主要是储能充放电状态数量有限两个求解器都能胜任。真正影响求解时间的是CCG的迭代次数和主问题规模。随着迭代进行主问题里的cut越加越多MILP规模线性增长。这个阶段要关注求解器参数设置Options.cplex.mip.tolerances.mipgap 1e-4; % 主问题MIP间隙 Options.cplex.mip.limits.nodes 1e5; % MIP节点数上限 Options.cplex.timelimit 300; % 单次求解时间上限设置时间上限很重要——CCG循环中如果某个主问题卡住了整个求解过程都会停滞时间上限可以保证循环能继续往下走不会卡死在一次求解里。4.3 滚动窗口让模型更贴近实际多阶段滚动相比单次全天求解还有一个很现实的好处可以更新预测数据。实际微电网运行中每过一个小时就能拿到新的风光实测数据滚动窗口里近期的预测误差范围可以收窄远期保持较宽这样调度方案“越近越准、越远越稳”经济性和鲁棒性之间的权衡比一次性模型合理得多。我在代码里用一个简单的数据更新机制模拟这个过程每个滚动窗口开始前把当前时段的实际出力抽取出来作为已知值下一时段起才是带不确定性的待测变量。这样SOC的递推链始终是由实测值驱动的不会累积一整天的预测误差。5. 常见问题与排查技巧实录5.1 CCG怎么收敛那么慢这个是最常被问的问题。子问题每次给主问题加的cut只有一个场景如果初始解离最优解太远迭代路径就会比较曲折。我的经验是给主问题加一个初始可行解用确定性场景跑一遍得到一组合理的机组出力和储能SOC作为CCG的初始解。这样能把迭代次数从二十多次压到十次以内。另一个原因是子问题求解不精确。子问题里的内层对偶问题求解完成后要把结果代回原问题验算确保目标值确实等于对偶问题的最优值。如果对偶间隙存在CCG的上下界关系就不成立收敛曲线会抖动。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”的排查路径如果某个滚动窗口的CCG迭代中途主问题报不可行最常见的三个原因一是前一个窗口传下来的SOC状态和当前窗口机组最小出力的组合无法同时满足二是风光出力区间在某个时段取叠加上下限后与机组爬坡约束冲突三是预算约束写错了方向。排查顺序建议先把不确定集退化成确定性模型如果还是不可行就是基础约束有问题如果确定性模型能解而加上不确定集后才不可行则在子问题返回场景时打印该场景的出力值和约束违约情况定位到具体的冲突时段。6. 实操中的体会这个模型复现下来最大的体会是鲁棒优化的难度不在理论而在工程。理论框架教科书上都写得很清楚min-max-min结构、强对偶、CCG知识点就那些。但真正动手写代码的时候双线性项的线性化怎么做才不伤数值稳定性、大M取多大、滚动窗口之间状态怎么衔接、不确定性预算选多少能在保守度和经济性之间找到平衡这些问题没有任何教材能给你标准答案全靠一遍遍调试积累经验。另外一点想说的是参数扫描很有必要。我通常会把不确定性预算Γ从2扫到10画一条“成本-保守度”曲线。你会发现它是一条单调递增的曲线但增长率会逐渐放缓。选择一个曲率变化明显的转折点作为实际采用的Γ值这样既不浪费经济性又具备足够的抗风险能力。这个判断方法比单纯拍脑袋取中间值靠谱得多。最后贴上这个项目X的代码时建议你先把不确定集部分注释掉用确定性模式对照着跑一遍能直观感受鲁棒模型和确定性模型的解有什么差别。差别不大说明不确定集构造得太松差别过大说明保守度需要降低。等调出这个手感鲁棒调度的内涵也就算真正吃透了。