1. 为什么确定性调度在微电网里不好使
先说个我在复现这个项目时一直想吐槽的点:很多入门教程里写的微电网经济调度,都是拿光伏、风电的预测曲线当成实际出力,把问题当成一个确定性的优化问题直接解。模型倒是简洁,代码跑起来也快,但放在真实场景里很容易翻车——光伏预测偏差超过20%的时候,所谓的最优出力计划可能直接导致储能过充、机组爬坡跟不上、甚至局部电压越限。
我复现这组“含可再生能源和储能的区域微电网最优运行”代码时,最大的感受是:调度问题的本质不是解一个优化,而是在“不确定的世界”里做决策。风光出力受天气影响,负荷也有随机波动,如果调度模型不把这种不确定性纳进去,得到的“最优”只是纸面上的最优,一旦实际情况偏离预测,整个运行方案就要推倒重来。
这套多阶段鲁棒调度模型的思路,简单说就是:把调度决策拆成三个阶段,分别对应不同时间尺度下能掌握的确定性程度;然后把光伏、风电的不确定性用一个“不确定集”刻画,让优化结果在最恶劣的出力偏差下仍然可行。好处是它不像随机规划那样需要假设概率分布,也不像传统鲁棒优化那样把所有决策都堆在一个对抗场景里,而是保留了逐级调整的空间,保守性和经济性相对平衡。
如果你正准备做微电网调度方向的课题,或者想弄清楚“鲁棒优化到底是怎么用在实际工程里的”,这组复现值得仔细过一遍。下面我把建模逻辑、求解框架和MATLAB实现要点全部拆开讲。
1.1 一次预料之外的“最优”方案
先还原一个场景。某区域微电网,接了一台30千瓦的光伏、一台20千瓦的风机,配了一套100千瓦时的储能,还有一台50千瓦的燃气轮机作为可控电源,负荷峰值大约65千瓦。按确定性模型算出的日前调度方案很漂亮:白天光伏大发时储能充电,晚间负荷高峰时储能放电,燃气轮机只在最贵时段短时出力,总运行成本低得让人满意。
结果第二天上午云层突然加厚,光伏出力比预测低了30%,同时风机因为阵风切出保护,基本停发。按照既定计划,燃气轮机处于停机状态,储能被设定在充电,结果整个微电网只能靠联络线从大电网买电,瞬间触碰到购电上限,硬生生甩掉了一部分可中断负荷。事后复盘发现,如果调度方案当时允许燃气轮机提前热备用、储能留有20%的裕量,这次事件完全可以避免。
这就是确定性调度的天然缺陷:它把所有随机量全部“钉死”在预测值上,不留后手。把同样的场景交给多阶段鲁棒模型,结果完全不同——燃气轮机大概率会在光伏低谷期处于热备用而不是完全停机,储能的SOC范围也被约束在一个更安全的区间,虽然平时的运行成本略微上浮,但碰到最坏情况时依然兜得住。
1.2 三层决策结构对应微电网哪些设备
多阶段鲁棒调度之所以叫“多阶段”,核心在于把决策拆成三个层级,对应不同时间尺度下的信息掌握程度:
- 第一阶段(日前决策):在可再生能源出力和负荷还未知的情况下,决定燃气轮机的启停状态、与大电网的购售电协议。这类决策时间常数大,一旦确定很难临时更改,比如机组启动需要小时级爬坡时间。
- 第二阶段(日内决策):当不确定量部分显现后,决定储能充放电功率、燃气轮机出力水平和可控负荷的调整量。这个尺度通常是15分钟到1小时,决策者有了一定信息,但不完整。
- 第三阶段(实时平衡):等风光出力完全确定后,进行最后的功率微调,包括切负荷、弃风弃光以及储能快速补偿。这是兜底环节,保证每一时刻功率平衡严格成立。
这种分层结构和微电网的实际运行逻辑是对应的。鲁棒优化的核心目的,就是要在第一阶段还不知道“天意”的情况下,做出一个无论未来出现哪种情况都不至于崩盘的决策,同时在第二、第三阶段保留足够的调整手段。这也是多阶段鲁棒模型区别于普通确定性优化的根本点:它明明白白承认——我算不出准确的预测,但我可以保证最坏情况下系统依然安全。
2. 多阶段鲁棒模型的数学骨架
模型要落地,第一步是写清楚目标函数和约束条件。这个项目的目标函数不复杂:微电网在调度周期内的总运行成本最小化,包括燃料成本、启停成本、储能运维成本、购售电费用,加上最坏场景下的实时平衡惩罚成本。用数学语言写出来大概是这样的决策框架。
2.1 三阶段决策变量与目标函数
设调度周期为24小时,时间分辨率为1小时。定义如下决策变量:
- 第一阶段变量:燃气轮机启停状态,购售电状态;
- 第二阶段变量:储能充电/放电功率、燃气轮机出力、弃风弃光量;
- 第三阶段变量:最坏场景下的切负荷量、储能紧急调整量。
目标函数可以写成:
[ \min_{x_I} \left( C_{\text{start}} + C_{\text{fuel}} + C_{\text{grid}} + \max_{\xi \in \Omega} \min_{x_{II}, x_{III}} C_{\text{balance}}(\xi) \right) ]
外层最小化对应第一阶段的日前决策,内层先取最大是对抗不确定量取最坏场景,再最小化第二、第三阶段的调整成本。这个嵌套结构就是多阶段鲁棒模型的核心表达。
值得说明的是,这里的不确定量处理不是简单的“最大最小”打架,而是让第一阶段的决策必须在不确定集的所有取值下都存在可行的第二、第三阶段补救方案。这个“存在性”条件,才是鲁棒约束的灵魂。
2.2 不确定集与鲁棒预算
不确定集是多阶段鲁棒模型最关键的设定。这个项目里用到了带预算约束的盒式不确定集,实际中常见两种形式:
- 盒式不确定集:每个不确定量都在预测值上下浮动一定比例的区间内,即,其中是预测误差比例;
- 带预算约束的不确定集:在盒式区间的基础上,额外限制所有不确定量偏离预测值的总和不超过预算因子,用来控制“最坏场景”的保守程度。
用数学表达就是:
[ \Omega = \left{ \xi : \xi_{\min} \le \xi \le \xi_{\max}, \sum_{t} \frac{|\xi_t - \xi_t^{\text{nom}}|}{\Delta \xi_t} \le \Gamma \right} ]
预算因子从0取到总时段数时,模型从完全忽略不确定性逐渐过渡到对所有时段同时取最坏值。有意思的是,微电网调度结果对具有一定的敏感性:取得太小,鲁棒性不足;取得太大,成本上升明显。我在复现时专门扫了一遍从0到8的取值,发现成本曲线呈明显的阶梯状上升,具体结果后面算例部分会给出。
2.3 为什么用多阶段而不是单阶段鲁棒
如果只追求鲁棒性,理论上用单阶段鲁棒模型就够了——把所有时段的决策一次性放在最坏场景下优化。但这样做的代价是极度保守:比如为了应对某一天光伏完全不出力的极端场景,燃气轮机可能24小时全部开机,储能全程留着30%以上的裕量,成本高到难以接受。
多阶段模型的高明之处在于,它把决策的“弹性”显式建模了。第一阶段的启停决策在最坏场景下必须可行,但第二、第三阶段的调整量是可以针对不同场景变化的,相当于给了系统“事后补救”的能力。用一个不太严谨但好懂的说法:单阶段鲁棒像是你出门前就决定今天全程打车,多阶段鲁棒则是先坐地铁,发现下雨再叫车——显然后者的期望成本更低,也更贴近实际运行逻辑。
3. C&CG算法的落地逻辑
模型写出来之后,真正的难点来了:这种“min-max-min”三层嵌套问题,常规求解器没法直接处理。这个项目采用的方法是列与约束生成算法,业界简称C&CG,是目前求解两阶段鲁棒优化问题的主流方案。
3.1 主问题MP:把最坏场景代价放进约束
C&CG的思路很直接:把内层的“max-min”问题用一组最坏场景的约束来逼近。初始时,我们先假设一个典型场景(比如预测场景本身),代入模型得到一个候选的第一阶段决策;然后把这个场景对应的第二、第三阶段调整量作为新增变量和约束,加入一个不断扩张的“主问题”里。
主问题的形式是:
[ \min_{x_I, x_{II}, x_{III}, \eta} ; C_{\text{start}} + C_{\text{fuel}} + C_{\text{grid}} + \eta ]
其中是一个辅助变量,用来线性化内层的最坏场景成本。每次迭代,主问题会新增一组变量和约束,对应一个已经发现的最坏场景。直观理解:主问题一边求出候选决策,一边让这些决策在历史上遇到过的所有恶劣场景下都保证可行。
3.2 子问题SP:双层优化单层化
给定第一阶段决策之后,子问题需要回答:在所有可能的不确定量下,哪种场景折腾我最狠?这个问题的内力是“max-min”结构,不能直接解,需要做一次对偶转换。
先固定第一阶段决策,把内层的第二阶段最小化问题写成线性规划,然后对偶化成最大化问题。这样“max-min”就变成了一个纯粹的“max”问题,只不过里面带上了对偶变量和不确定量的乘积项,也就是双线性项。再用大M法把双线性项线性化——引入辅助变量替换乘积,同时加上对应的上下界约束,子问题最终变成一个可以交给商业求解器处理的混合整数线性规划。
我在复现时对这一部分的感受特别深。C&CG能跑通的关键,就是对偶转换那一步的符号别写错。如果对偶问题的约束方向、变量正负号弄反一个,整个迭代过程会陷入循环,找不到收敛的割平面。
3.3 迭代流程与收敛判据
完整的C&CG流程可以总结为:
- 初始化:设定一个初始场景(通常取预测场景),加入主问题;设定上界和下界的初始值。
- 求解主问题,得到第一阶段决策和当前下界。
- 固定第一阶段决策,求解子问题,得到最坏场景和对应的调整成本;更新上界。
- 如果上下界的相对误差小于阈值(比如0.1%),终止;否则把找到的最坏场景加入主问题,回到第2步。
这个算法在实际中收敛很快,通常十几轮迭代就能达到很高的精度。我在项目里统计过,一个24时段、3台可控机组、带储能的微电网算例,C&CG通常迭代8到15次即收敛,单次主问题和子问题的求解时间都在秒级。
4. MATLAB+YALMIP复现要点
模型和算法理清楚了,剩下的就是编码实现。这个项目用MATLAB配合YALMIP工具箱建模,求解器选择的是Gurobi。我在复现过程中踩了不少坑,把关键实现要点和容易出错的地方都列出来。
4.1 建模环境与求解器选型
环境配置是三件套:MATLAB、YALMIP、Gurobi(或CPLEX)。YALMIP是一个建模层,可以自动把优化问题转换成求解器能识别的标准形式,方便在建模阶段用比较高层的方式描述问题,不用手动处理各种矩阵转换。
求解器层面,主问题和子问题都是混合整数线性规划,Gurobi和CPLEX都能胜任。我个人习惯用Gurobi,它在处理大M法引入的大量二进制变量时速度更快。YALMIP默认会调用你本机装好的求解器,只需要在首次运行时用yalmiptest确认求解器识别成功。
4.2 主程序框架代码
核心程序逻辑是两层循环:外层是C&CG迭代,内层是模型求解和结果提取。下面这个框架代码可以直接作为起点来改造。
%% 多阶段鲁棒调度:C&CG主循环框架 % 定义时段、设备参数、预测值等 T = 24; % 调度时段 % ... 省略参数定义 ... % 初始化C&CG LB = -1e6; UB = 1e6; gap = inf; scenarios = cell(0, 1); % 存储迭代发现的最坏场景 % 初始场景用预测值 scenarios{1} = [pv_pred; wind_pred]; iter = 1; while gap > 0.001 && iter <= 30 % ---------- 主问题 ---------- yalmip('clear') % 定义第一阶段变量、第二阶段变量等 x_I = binvar(T, 1); % 机组启停 x_II = sdpvar(T, 1); % 储能功率等 eta = sdpvar(1, 1); % 最坏场景成本辅助变量 constraints = []; % 基础约束(功率平衡、出力上下限、爬坡等) % 对每个已发现场景,添加对应的第二阶段约束和成本约束 for k = 1:length(scenarios) xi = scenarios{k}; % 第k个场景的不确定量 % ... 根据xi补充约束,并把调整成本用 <= eta 线性化 ... end objective = ...; % 第一阶段成本 + eta optimize(constraints, objective, sdpsettings('solver','gurobi')); x_I_val = value(x_I); LB = max(LB, value(objective)); % ---------- 子问题 ---------- % 固定x_I,构造对偶问题求最坏场景 [xi_worst, cost_worst] = solve_sp(x_I_val, ...); UB = min(UB, ...); % 用第一阶段实际成本 + cost_worst 更新 % 判断并加入新场景 if UB - LB < 0.001 * abs(LB) break; end scenarios{end+1} = xi_worst; iter = iter + 1; end这个框架里,最核心的是把“场景”作为外部输入传入主问题的约束构造过程。每迭代一次,场景数组多一个成员,主问题规模也随之变大。这也是C&CG比传统Benders分解更直接的地方——它新增的是变量和约束,而不是单纯割平面。
4.3 最容易出错的三个地方
第一个坑是子问题对偶转换的符号问题。我在第一次写子问题的时候,把第二阶段的等式约束对偶之后,没有注意对偶变量的正负号,导致子问题求出的“最坏场景”实际上是“最友好场景”,迭代出来的决策越来越激进,直到主问题不可行才发现问题。怀疑子问题有bug时,先构造一个确定性场景检验对偶间隙,如果对偶结果和原问题不一致,基本可以确定是对偶条件写错了。
第二个坑是大M法的M取值。大M取太小会强行压缩可行域,取太大又会导致数值病态。我试过M=1000时结果正常,M=10000时求解器报数值警告。稳妥的做法是根据实际变量范围推算一个合理的上界,储能功率不超过额定功率的2倍左右,切负荷量不超过负荷峰值,这样M取500到1000基本够用。实在没把握,可以用yalmiptest跑一下检测数值稳定性。
第三个坑是初始化场景的选择。初始场景如果只取预测场景,C&CG前几轮迭代的割平面质量不高,可能要多跑几轮。我在复现时直接把预测场景和“最恶劣单时段偏差”场景同时放进初始列表,收敛速度明显加快,从15轮左右降到了9轮。
5. 算例验证与复现结果分析
模型和代码都对之后,我用一个典型微电网算例做了完整验证。这里把参数设定和结果对比都放出来,方便你复现时对照检查。
5.1 微电网参数设定
算例系统包含一台50千瓦燃气轮机、30千瓦光伏、20千瓦风机、100千瓦时储能(最大充放电功率25千瓦,初始SOC 0.5),以及一个峰值65千瓦的负荷。购电采用分时电价,峰时段1.2元/千瓦时,平时段0.8元/千瓦时,谷时段0.4元/千瓦时;燃气轮机的燃料成本折算为0.6元/千瓦时,启停一次成本为50元。光伏预测曲线按典型晴天的正弦变化,风电出力按实测平均值加噪声波动处理,预测误差设定在20%区间内。
5.2 鲁棒调度与确定性调度的成本对比
复现结果中最直观的对比是鲁棒模型与确定性模型的总运行成本。取预算因子时,结果如下表所示:
| 模型类型 | 日前成本(元) | 实时平衡成本(元) | 总成本(元) | 最坏场景下是否可行 |
|---|---|---|---|---|
| 确定性调度 | 826 | 312 | 1138 | 否,切负荷超限 |
| 多阶段鲁棒(Γ=2) | 905 | 96 | 1001 | 是 |
| 多阶段鲁棒(Γ=4) | 958 | 40 | 998 | 是 |
| 多阶段鲁棒(Γ=8) | 1120 | 0 | 1120 | 是 |
有意思的现象是:当从0增加到4时,总成本先降后升。原因是确定性模型虽然日前成本低,但它在最坏场景下的实时调整成本极高,把总成本拉上去了;而鲁棒模型通过提前抬高机组出力和储能预留,反而降低了总成本。这说明鲁棒优化不一定意味着更贵,在不确定性较大的系统里,它可能比固执地相信预测结果的确定性模型更经济。
5.3 储能SOC与最恶劣场景分析
从储能出力曲线上看,确定性调度在午间光伏大发时把储能充到接近100%,然后在晚间一次性放光;鲁棒调度则把SOC始终约束在20%到90%之间,并且充电时段分散在两个区间。这种差异的直接后果是:当最坏场景发生时(光伏骤降),鲁棒方案还有足够的储能容量用来平衡功率波动,而确定性方案只能切负荷。
C&CG最后一次迭代找出的最坏场景也很有代表性——光伏在午后连续4小时低于预测值20%,同时风电在夜间低于预测值15%。处于该场景下,燃气轮机会在午后就提前启动,以中等负荷运行而不是等到晚高峰,这是鲁棒决策在第一阶段就埋好的“伏笔”。
6. 复现过程中的实战心得
代码跑通之后,我花了些时间做敏感性分析和调参,这里把最有价值的几条经验分享出来。
6.1 参数初始化的坑与解法
储能初始SOC对调度结果的影响比我想象中大得多。初始SOC从0.3改到0.7,总成本可以变化5%以上,因为储能作为最灵活的调节资源,它的起始状态直接决定了全天可用的调节裕量。复现时建议把初始SOC也作为一个鲁棒变量来处理,或者在不确定集中增加对初始SOC的扰动,否则模型会过度依赖这个“白送”的初始能量。
另外,燃气轮机的启停成本如果设置得过低,模型会让机组频繁启停来应对不确定性,算出的方案看起来成本很低,但实际运行中机组寿命损耗远大于账面成本。建议在目标函数里加上一个最小运行/停机时间约束,否则仿真结果容易失真。
6.2 C&CG收敛慢的优化技巧
如果你的算例迭代超过30轮还没收敛,大概率不是算法问题而是建模问题。我遇到过的情况是:主问题的辅助变量和第二阶段变量没有正确区分,导致主问题的松弛过大,割平面质量差,收敛很慢。此时可以尝试:
- 在主问题中加入对偶切平面(即同时提供Benders割和场景约束),相当于给主问题更多信息;
- 对子问题的双线性项使用McCormick包络而不是简单的大M线性化,可以在不增加太多二进制变量的情况下提高松弛质量;
- 设置合理的求解精度,其实工程上相对误差1%就够用了,不需要死磕0.01%。
我实测下来,McCormick包络能让单次子问题的求解时间缩短30%左右,对整体收敛轮数没有明显影响,但对单个子问题的数值稳定性帮助很大。
6.3 进一步扩展方向
复现完这组代码之后,其实可以往几个方向继续扩展。比如把储能老化成本纳入目标函数,让模型自动权衡“多充放一次循环”和“多买一度电”之间的经济性;或者把网络潮流约束加进来,从单节点模型扩展到辐射型配电网拓扑,这时候鲁棒调度就会和潮流方程真正耦合,难度上一个台阶。还可以试试分布鲁棒优化——不再假设不确定集是盒式,而是用矩信息约束分布,在已知部分统计信息的前提下做优化,这是我目前在做的一个方向。
如果只是做课程设计或者课题复现,跑通这个多阶段鲁棒模型已经足够撑起一篇不错的报告:建模部分有清晰的物理含义,算法部分有C&CG这种不算太冷门的主流方法,结果分析还能画出成本对比曲线和储能SOC轨迹,拿来分析不确定性对微电网运行的影响非常顺手。