2026/10/11 3:33:58

微电网两阶段鲁棒优化经济调度:MATLAB建模与CCG求解实践

微电网两阶段鲁棒优化经济调度:MATLAB建模与CCG求解实践 微电网的“两阶段鲁棒优化经济调度”这几年在电力系统方向几乎成了研究生开题和工程验证的标配题目。原因不难理解微电网里光伏和负荷的预测误差总是存在确定性经济调度模型算出来的方案在实际运行中经常因为预测偏差过大而直接失灵而两阶段鲁棒优化能在给定不确定性集合内寻找到一个无论最坏情况怎么发生都能“兜得住”的调度策略同时相比传统单阶段鲁棒又不会保守到让人无法接受。这篇文章我会以自己在 MATLAB 里实现这套算法的完整过程为线索把建模、求解方法、代码结构、参数调试和常见坑逐一讲清楚内容适合正在做微电网能量管理的研究生也适合做园区微电网二次开发的工程师参考。1. 为什么要用两阶段鲁棒优化而不是常见的最小化成本模型1.1 确定性调度方案的最大问题预测不准就翻车常规的微电网经济调度通常假设光伏出力、负荷功率是已知的确定值然后在此基础上做机组组合、储能充放电计划和与主网交互的功率曲线。你用的是历史数据的期望值也好短时预测值也罢本质上都是把不确定量固定成了一个点。问题是实际运行中光伏在早高峰和下午阵雨之间的出力差距可以超过 60%负荷侧一个充电桩集群同时接入就能让预测曲线明显失真。这时候确定性方案的“最优”就变成了纸面最优现场很可能出现功率失衡、切负荷或者频繁启停柴油发电机不光运行成本上去了设备寿命也被拖累。我在第一次做这类项目时曾经直接把预测曲线当成真实值跑出一个漂亮的 24 小时调度计划。结果某天下午实际光伏出力比预测低了约 30%储能在前半夜已经把电放完了晚间负荷高峰只能高价从主网买电当天成本比预期高出近两成。这个经历让我意识到如果不把“预测可能偏差”作为约束的一部分放进去调度结果就没有任何鲁棒性可言。1.2 两阶段鲁棒优化到底在优化什么把问题拆成两个阶段是这类模型的精髓。第一阶段在“不确定量还没揭晓”的时间点做决策最典型的就是机组启停和储能充放电状态的安排这些变量要么是 0/1 离散值要么需要提前锁定不能在知道光伏实际出力后再改变。第二阶段则是在不确定性“兑现”之后微电网再做实时调整比如调整机组出力、调整储能功率、向主网买电或卖电、甚至切除一部分柔性负荷。两阶段鲁棒优化的目标从数学上讲就是最小化“第一阶段的投资或运行成本 最坏不确定性场景下第二阶段的最小调整成本”。注意这里有个嵌套的 min-max-min 结构外层求最小化总成本中间对不确定性求最大化最内层对第二阶段决策求最小化。单阶段鲁棒为求稳妥往往把第二阶段所有可能出现的调整都提前绑死在约束里导致方案非常保守两阶段模型因为引入了“看到不确定量后再调整”的自由度经济性会明显更好这也是它被广泛采纳的核心原因。1.3 与随机规划相比鲁棒优化适合的场景不少朋友会问我为什么不直接用随机规划给光伏、负荷分别设几十个场景随机规划的优势是能利用历史场景的概率分布信息但前提是分布估计准确。实际中很多时候你只能拿到预测区间或者只知道偏差的上下界强行构造概率分布反而引入新的误差。鲁棒优化恰好不依赖精确分布只要不确定性集合刻画合理给出的方案就能在所有集合内场景下可行。代价自然是结果偏保守但可以用“预算参数”控制保守程度这是工程上非常好用的一点。我个人的经验是当你有大量历史场景且分布稳定时优先选随机规划当你想保证最坏情况也安全、又不希望解太保守的时候两阶段鲁棒是最合适的折中方案。2. 建模思路先搞清楚第一阶段和第二阶段分别管什么2.1 微电网系统的典型组成与参数在搭建模型之前先把系统的物理组成定下来。这里以一个典型的交流微电网为例包含一台柴油发电机、一组锂电池储能、一片光伏阵列、若干不可控负荷和柔性可中断负荷同时通过公共连接点与主网相连。对外购电、售电的价格按分时电价处理光伏发电成本近似为零但出力有上限且不可控。柴油机有最小技术出力、爬坡速率限制储能则有容量上下限、充放电功率上限以及充放电效率差异。对于这种规模的系统MATLAB 建模并不复杂但变量维度会随着时段数快速增长。以 24 小时调度为例第一阶段变量包括柴油机启停、储能充放电状态等 0/1 变量第二阶段变量包括各时段机组出力、储能功率、主网交互功率和切负荷量变量总数很容易超过 500 个非线性、非凸约束也需要做精细处理。2.2 第一阶段机组组合与设备状态第一阶段决策本质上是“承诺”确定哪些机组在哪个时段启动、哪些储能时段允许充电或放电。这个阶段要付出的是固定成本比如启停成本和基础维护成本。拿储能来说如果不考虑退化成本可以把充放电状态当作第一阶段变量如果考虑寿命损耗还要给充电、放电各增加一个 0/1 状态变量并把充放电功率写成状态变量与连续变量的乘积这一步会引入双线性项需要做线性化稍后我在实现部分会专门讲。第一阶段还有一个容易忽略的点柴油机启动后一般不能瞬间停机必须满足最小运行时间和最小停机时间约束。调度周期如果不是足够长这类约束在建模时需要从初始运行时间开始递推。很多人在 MATLAB 里实现时漏掉这些时间耦合约束结果跑出来的调度结果在实际操作中根本执行不了这个坑很典型。2.3 第二阶段不确定量显现后的实时再调度进入第二阶段光伏出力的实际值、负荷实际值从不确定集合里“取”了一个具体值微电网需要在前述第一阶段决策基础上通过调整可控机组出力、储能功率和主网购售电功率来维持功率平衡。第二阶段的平衡约束是紧约束也就是每个时段都要求发电、购电、放电之和等于负荷、售电、充电之和。如果不平衡就引入可中断负荷削减量并施加相应的惩罚成本。第二阶段的目标函数通常是调整成本最小化对可调度机组采用线性或分段线性成本函数对购电采用分时电价对切负荷采用高额惩罚成本。关键在于第二阶段的问题要在“最坏场景”下被评估因此不是你直接给一个确定的光伏和负荷值而是要先去不确定集合里搜索使总成本最大的那组光伏、负荷曲线。2.4 不确定性集合构造与保守度控制不确定集合是鲁棒优化的灵魂。常见的构造方式有三种盒式集合、盒式加预算约束的集合、以及基于历史预测误差统计的分段集合。盒式集合简单但要求每个时段的光伏、负荷都取到边界这在物理上其实很少出现因此方案过度保守。实际工程中我更推荐在盒式基础上加入预算约束。预算约束的思想是在一个调度周期内实际偏差超过预测基准的时段数是有限制的。比如光伏有 24 个时段预测偏差为 ±20%预算参数设为 6就表示最多只有 6 个时段会出现 20% 的负偏差其他时段要么无偏差、要么偏差更小。这样一来不确定性集合从“超立方体”切掉了一些极端的角既保留最坏情况的代表性又显著降低保守性。设光伏和负荷的不确定性集合参数分别为 Γpv 和 Γload取值越大越保守。具体怎么选我的经验是先用确定性模型得到基准解然后从低预算开始逐渐增大观察总成本的变化趋势在成本增长的“拐点”附近确定最终值。这比拍脑袋取边界值要可靠很多。总体而言完整的目标函数可以写为min (第一阶段成本 max_{u∈U} min_{y∈F(y,x,u)} 第二阶段成本) s.t. 第一阶段约束其中 u 表示光伏和负荷的不确定量y 表示第二阶段运行变量F 是第二阶段可行域。这个 min-max-min 结构就是两阶段鲁棒优化的标准形态。3. 求解方法列约束生成CCG为什么比 Benders 分解更合适3.1 主问题与子问题的循环结构两阶段鲁棒优化直接求解非常困难因为它本质上是一个多层嵌套优化问题。实际工程和论文里最常用的是列约束生成法CCG也叫 CCG 算法它把原问题拆成主问题和子问题通过迭代方式逼近最优解。主问题是一个包含第一阶段变量和若干已经识别出来的“最坏场景”的混合整数规划问题。每轮迭代主问题中都会新增一组对应于某个具体不确定性场景的第二阶段变量和约束因此主问题的目标值从数学上看是原问题的下界。子问题则是在给定第一阶段决策后去搜索不确定集合中使第二阶段调整成本最大的那个场景并返回最优值。子问题的目标值加上第一阶段的真实成本之和构成原问题的上界。当上界和下界的相对间隙小于设定阈值就认为迭代收敛。这段逻辑用 MATLAB 的伪代码写出来核心循环大概只有二十来行LB -inf; UB inf; Gap 1; k 0; while Gap 1e-3 k 30 k k 1; % 求解主问题得到第一阶段决策 x 和下界 LB [x, LB] solveMasterProblem(scenarios); % 固定 x求解子问题得到最坏场景 u_star 和第二阶段最优值 [obj_sub, u_star] solveSubproblem(x); % 计算当前上界第一阶段成本 最坏场景下第二阶段成本 UB_temp first_stage_cost(x) obj_sub; UB min(UB, UB_temp); % 将 u_star 作为新的场景加入主问题 scenarios [scenarios, u_star]; Gap (UB - LB) / abs(LB); end每次迭代向主问题加入一个新场景场景数量不断增长主问题规模也随之变大。但 CCG 的收敛速度通常很快实际算例往往只需要 3 到 8 次迭代就能把间隙降到 1% 以内。3.2 子问题如何“找到”最坏场景子问题的本质是 max-min 问题MATLAB 里不能直接调用求解器处理这种嵌套结构。标准处理方法是把内层 min 通过线性规划对偶转化为一个 max 形式于是子问题就变成纯最大化问题可以直接对偶求解。这里最关键的步骤是用强对偶性把内层最小化问题替换成它的对偶问题。以连续变量为主的第二阶段问题只要满足 Slater 条件强对偶是成立的。替换之后子问题中的目标变成了第三阶段的对偶目标约束变成了原约束的约束以及对偶变量的符号约束。与此同时第一阶段决策 x 作为已知参数出现在约束中因此子问题仍然能搜索 u 来最大化目标这正好对应“寻找最坏场景”的语义。CCG 和经典 Benders 分解的一个重大区别也在这里Benders 分解是在子问题求解后向主问题添加割平面但每次只增加一条约束CCG 则是直接添加一整组对应于最坏场景的新变量和新约束。从数值意义上讲CCG 在有限次迭代内更容易收敛特别是混合整数主问题场景下CCG 的稳定性明显更好。我在做这个项目时一开始用的是 Benders 分解跑了十几轮还在振荡换成 CCG 之后六轮之内就稳定收敛了。如果你的问题规模不大性能差异不是最关键的但 CCG 的收敛速度会显著减少调试时间。3.3 主问题的规模增长控制也不要高兴太早CCG 每次迭代都会给主问题添加一整组变量迭代次数一多主问题会变得非常庞大。比如你的第二阶段变量有 200 个迭代 10 次主问题里就多了 2000 个变量和数千条约束。所以实际使用中要给迭代次数设上限并动态设置最优性间隙。另外一个加速技巧是给主问题设定一个相对较大的间隙容差比如 1e-2让主问题不必每次求解到最精确。主问题精度稍微放宽并不会破坏最终收敛因为上界由子问题决定下界只要在合理的松弛范围内即可。这样做的好处是主问题的 MIP 求解时间能下降 30% 到 50%而总迭代次数几乎没有变化。3.4 收敛判据怎么设才合理收敛判据通常用相对间隙(UB-LB)/|LB|表示。这个值设太大比如 5%方案可能与最优偏差明显设太小比如 1e-5主问题规模增长带来的计算时间浪费又很严重。我的默认做法是设 1e-3最多迭代 15 次。如果超过 15 次还没收敛优先去查子问题有没有对偶化错误而不是继续放大迭代次数。我在实际调试中遇到过一次迭代十几次不收敛最后发现是把子问题里一个非负变量错误地声明成了自由变量导致子问题目标值虚高UB 一直降不下来。这类问题不把对偶公式展开逐项检查几乎不可能定位到。4. MATLAB 实现中的关键细节与代码设计4.1 环境配置与求解器选型MATLAB 里做优化建模我推荐直接用 YALMIP 工具箱它可以把优化模型用接近数学表达式的形式写出来然后通过优化函数调用底层的商业求解器。主问题通常是混合整数规划底层求解器建议使用 Gurobi 或 CPLEX子问题对偶化后是连续线性规划用同一种求解器也可以但在精度参数上有讲究。安装 YALMIP 和求解器时最容易出问题的点在于路径配置。YALMIP 的根目录要添加到 MATLAB 搜索路径求解器接口也要确保能被 YALMIP 正确识别。一个实测有效的检查方法是在命令行输入需求解器命令并查看对应的接口是否存在如果提示找不到多半是求解器的 MATLAB 接口版本和 YALMIP 版本不兼容。稳妥的做法是尽量选择同一时期的稳定版本避免用最新版 YALMIP 搭配很老的求解器接口这种组合常出现莫名的传参错误。4.2 变量定义与线性化处理在 YALMIP 中第一阶段变量用binvar定义第二阶段连续变量用sdpvar定义。储能充放电状态这个典型场景里充电功率 P_ch 可以写成 0/1 状态变量 z_ch 和额定功率 P_max 的乘积但 z_ch 与 P_ch 相乘是双线性项在 MIP 里必须线性化。线性化的标准做法是引入辅助连续变量并把乘积用四组不等式约束替代当 z0 时辅助变量为 0当 z1 时辅助变量等于 P。比如令 w z * P则w P_max * z; w 0; w P; w P - (1-z) * P_max;四组约束合起来就刻画了 w z * P 的等价关系。这个技巧在储能、可中断负荷、机组最小出力等几乎所有带状态和幅值的场景中都会用到务必熟练。我在最初实现时图省事直接把 P_ch 和状态变量分别约束没想到跑出来储能在同一天里既充电又放电功率平衡还显示正常排查了半天才发现是二进制状态缺失导致模型出现了不物理的环流。4.3 子问题的对偶化与代码结构子问题实现是整个 MATLAB 代码里最需要耐心的部分。第一步是把内层 min 问题写成标准形式识别出决策变量、约束矩阵和右端项第二步写出对偶问题注意变量上下限约束会产生对偶变量的正负号第三步在 YALMIP 中直接构造包含不确定量 u 和目标函数的最大化模型。构造子问题模型时不要用循环逐条添加约束那样代码冗长且容易出错。更好的做法是把所有时段变量整理成向量用矩阵运算一次添加整组约束。这样代码可读性高而且 YALMIP 处理向量约束的效率更高。我个人习惯写成独立的函数输入是第一阶段决策向量和系统参数输出是最坏场景和子问题目标值。把主问题和子问题拆成两个独立函数后面调试定位问题要轻松得多。4.4 主问题动态扩展的实现技巧CCG 的主问题每一轮都需要新增一组变量约束。新手最容易写的写法是这样的每轮迭代都重新调用一次sdpvar定义一整组新变量然后把所有历史变量和约束全部重新拼接到模型中。这在一个 24 时段、三四个设备、迭代十次的算例里还能跑但一旦扩展到 96 时段或更多设备MATLAB 的符号变量爆炸会让你等到怀疑人生。更高效的做法是利用 YALMIP 的增量建模特性在主问题初始化时先定义第一阶段变量和对应约束之后每轮迭代只添加新场景对应的变量和约束然后调用optimize求解。每一步只增加新变量组不需要重新生成已经存在的符号对象内存占用和求解时间都能得到明显改善。这个方法我实测在 96 时段规模下能减少约 60% 的主问题建模时间。4.5 储能 SOC 约束的时域耦合处理储能荷电状态约束是时域耦合约束每个时段的 SOC 等于上一时段 SOC 减去放电量除以放电效率再加上充电量乘以充电效率。写成数学表达式是一个简单的递推式但在 YALMIP 里需要按时间顺序构建for t 2:T Constraints [Constraints, SOC(t) SOC(t-1) ... - P_dis(t)/eff_d P_ch(t)*eff_c]; end除了递推关系还要注意 SOC 在每个时段的上下限约束以及调度周期结束时希望 SOC 回到初始值附近否则下一个周期的调度起点会出现偏差。如果目标函数里对 SOC 末值没有硬性约束通常的做法是在末端时段添加 SOC 等于初始 SOC 的约束或惩罚项。这个递推约束看似简单但它把整个时段链的变量耦合在一起如果不小心把效率参数放在错误的位置比如放电效率写成效率乘以放电量累积下来 SOC 会出现缓慢漂移调度方案的储能轨迹看着正常实际却违背能量守恒。检查办法很直接跑一个无不确定性、光伏负荷都为预测值的确定性案例验证是否满足能量守恒约束再进入鲁棒优化流程。5. 算例设置与结果分析5.1 算例参数与不确定性设置为了把前面的方法落地这里给出一组典型的算例参数。系统含一台 300 kW 柴油发电机、一套 200 kW / 400 kWh 储能、500 kW 光伏、最大负荷约 600 kW与主网的交互功率上限为 800 kW。柴油机发电成本取分段线性函数边际成本约 0.8 至 1.2 元/kWh主网购电价格白天峰时 1.1 元/kWh夜间谷时 0.4 元/kWh。光伏预测曲线按典型晴天曲线生成负荷曲线按典型工作日负荷曲线生成。不确定性集合设光伏预测偏差为 ±20%负荷预测偏差为 ±10%预算参数 Γpv8Γload6。显而易见这是一个中高保守度设置。求解平台就是 MATLAB YALMIP 商业求解器收敛间隙设为 1e-3最大迭代次数 15。5.2 确定性方案与两阶段鲁棒方案对比把该算例分别用确定性模型和两阶段鲁棒模型求解决策差异主要体现在机组启停和储能充放电时段安排上。确定性模型会在光伏预测高、负荷预测低时把柴油机安排成机组启动用更多的光伏出力覆盖负荷两阶段鲁棒模型则会在最坏场景下考虑光伏出力缩水、负荷抬高的情况因此柴油机不会盲目停机储能也会保留更多电量应对晚间负荷高峰。从成本数据上看确定性模型给出的方案在预测场景下运行成本约 1.45 万元但这个方案在按最坏场景重放时成本会飙到 1.83 万元同时出现约 40 kWh 的失负荷量。两阶段鲁棒模型的方案在最坏场景下的成本约 1.68 万元失负荷量为零。也就是说鲁棒方案只比确定性方案在预期场景下贵了约 12%却换来了最坏场景下不失负荷的安全保障同时把最坏情况成本控住了。如果只盯着确定性成本去选方案极端场景下的代价会让你后悔莫及。5.3 预算参数对结果的影响我还专门测试了 Γ 参数从 0 到 24 变化时的成本曲线。Γ0 时不考虑不确定性模型退化为确定性模型Γ24 时所有时段都允许最大偏差即完全保守的盒式集合。成本变化大致呈阶梯上升Γ 从 0 增加到 6 时成本增长较快此后曲线明显放缓。这说明对保守度的边际收益递减实际工程取 Γ 在峰值时段数量的 1/4 到 1/3 往往就能获得不错的稳健性不必追求极端保守。这类敏感性分析对于说服项目评审也很有价值。我建议在交付结果时把总成本、失负荷量、储能 SOC 曲线、柴油机启停计划这几个维度各出一张对比图既能展示算法效果也能暴露模型问题。6. 常见问题与排查技巧实录6.1 主问题规模增长后求解太慢这个现象几乎人人都踩过。直观反应是减少迭代次数但更高效的做法是先把主问题的 MIP 间隙容差调大一点比如optimize(Constraints, Objective, sdpsettings(solver,...,gaptol,1e-2))。要知道主问题只是下界没必要每次都求到极致。另外把每个场景的第二阶段变量用矩阵方式组织也能减少 YALMIP 的符号运算时间。最坏的情况是迭代超过 20 次仍然不收敛这时候基本上可以确定是子问题算错了不是调参能解决的。6.2 储能充放电同时为“正”的问题我在 4.2 节提到过线性化的坑这里再补充一个排查技巧在输出结果里单独绘制出充放电功率和储能状态变量曲线如果充电和放电在某些时段同时出现正值先检查是否已经定义了两个互斥的二进制状态变量并加入 z_ch z_dis 1 的约束。这一步缺失时储能的行为从数学上可以“既充又放”两个方向的功率互相抵消模型隐式地制造了一个零成本的能量环流导致优化结果失真。即使功率平衡约束看起来满足这个伪造的自由度也会扭曲经济性判断。6.3 对偶子问题的符号容易看错第二阶段目标函数如果含有正负系数写对偶时特别容易把不等号方向搞反或者把非负约束丢在一边。建议对子问题里的每个变量单独列一张表标明原始变量类型、对应的对偶变量以及符号。如果子问题求解结果出现“无界”或目标值异常大先检查是不是漏了某条边界约束如果出现不可行先检查是不是第一阶段给定了不可执行的启停计划。这类调试不能靠猜把小算例手工算一遍和程序结果对照是最快的定位手段。6.4 求解器数值稳定性问题MATLAB 默认会把数值单位交给模型自动处理但微电网模型中功率和电量的尺度差异很大功率是 kW 级SOC 是 kWh 级成本系数可能是元/kWh 级。这些问题叠加后约束矩阵的条件数可能变得很大求解器数值误差会明显上升。我建议在建模时统一把功率单位换算成 MW、电量换算成 MWh时间用小时成本用万元或千元让所有系数保持在接近 1 到几百的范围。实测做过一次单位归一化后同一个算例的求解时间下降了约 40%数值警告也几乎消失。6.5 结果不合预期的常见检查清单如果你跑出来的调度结果看起来违反常识可以按这个顺序检查先确认功率平衡约束的符号方向再检查储能的充放电效率设置然后看不确定集合的预算是否设置过大最后检查子问题返回的最坏场景是否符合物理直觉比如最坏场景是否对应于光伏低、负荷高但分布不合理的组合。大多数“奇怪”结果都能在这些检查里找到答案。现象可能原因确认方法主问题不收敛或迭代很久子问题对偶化错误小算例手工验证目标值储能同时充放电缺少互斥状态约束绘制功率曲线直视检查SOC 越界或漂移效率参数位置写错确定性场景核对能量守恒最坏场景不直观不确定集合参数设置不当输出场景曲线并逐项核对求解报数值错误模型单位尺度差异过大统一功率、电量、成本单位7. 实操经验与后续扩展方向整套两阶段鲁棒优化在 MATLAB 里从建模到收敛我现在基本可以在一天内完成但这个过程踩过的坑确实不少。回头总结最核心的经验有三条第一第二阶段子问题必须先做对偶化验证而不是急着整主问题循环第二模型单位归一化收益巨大尤其在 96 时段以上规模数值稳定性直接影响求解速度第三CCG 的收敛性高度依赖不确定性集合的合理设置预算参数不要拍脑袋要做敏感性扫描。对于想继续扩展的朋友有几个方向值得探讨。一是在第二阶段引入需求响应把可中断负荷建成可调变量这样最坏场景下的切负荷惩罚可以显著降低二是把储能寿命衰减成本放进目标函数避免优化结果过度压榨储能循环寿命三是在不确定性集合里加入时间相关性约束比如相邻时段光伏出力偏差不能频繁跳变这样可以进一步降低保守性。我个人认为最后一项在实际工程中价值最大因为真实光伏出力的偏差是连续变化的用独立的盒式约束忽略了时间连续性会导致最坏场景过于严苛。如果有人想用这个代码框架去做更高阶的研究一个最直接的改动是把第二阶段从线性模型换成混合整数模型比如加上需求响应的状态变量这样 CCG 的处理会复杂不少但换来的经济性提升非常明显。在动手前务必先在小算例上验证对偶公式不然一旦规模扩大错误会被快速放大调试成本也随之飙升。我自己在后续版本中加入了分段线性购电成本和储能退化成本收敛速度没有明显恶化但方案的经济性评价明显更贴近实际运行情况。这就是两阶段鲁棒优化最吸引人的地方建模深度可以逐级加深核心算法骨架却始终稳定可靠。