“多微网联合调度及容量配置”这个概念,我在圈子里反复听到,但不夸张地说,真正把它和两阶段鲁棒优化算法结合起来、还能在MATLAB里跑通的工程案例,确实不多。很多朋友手里拿着论文,知道鲁棒优化能处理不确定性,可真要自己搭模型、写代码、调收敛,往往在第一步就卡住了。
这篇博文我想从一个实操者的角度,把我自己做过的一套MATLAB实现方案完整拆给你看:多微网联合调度怎么建模,容量配置为什么要用两阶段鲁棒优化,不确定集合怎么设计,代码结构怎么组织,以及调试过程中那些教科书上不会写的坑。内容偏向工程落地,适合已经有微网调度基础、想进阶到鲁棒优化层面的同学,也适合正在写相关仿真代码、需要复现两阶段算法的朋友。
1. 项目概述与问题拆解
1.1 多微网联合调度的核心需求是什么
先说清楚这个项目到底在解决什么问题。一个微网通常包含光伏、风电、储能、柴油机或燃气轮机,还有本地负荷。单个微网独立运行时,容量小、调节手段有限,光伏一波动就容易出现功率失衡。如果把几个相邻微网通过联络线组成一个微网群,互相支援余缺,整体运行的可靠性和经济性都会好很多。
但联合起来之后,问题复杂度成倍上升。每个微网有自己的设备、自己的负荷、自己的利益诉求,同时还要在公共联络点上交换功率。调度中心需要决定:每台机组出多少力、储能充放电策略怎么定、微网之间交换多少功率、要不要切负荷。这些决策叠加在一起,就是一个典型的多时段、多主体、强耦合的优化问题。
容量配置这个问题更靠前。微网里光伏和储能到底该建多大容量,柴油机要不要扩容,这些属于投资层面的长期决策。投资决策一旦定下来,后面每一天的运行调度都要跟着这个容量走。所以两个问题不能分开做:你先把容量定得太大,运行调度可能很宽裕,但初始投资高得离谱;容量定得太小,运行阶段天天切负荷,惩罚成本反而更高。
1.2 为什么选择两阶段鲁棒优化,而不是确定性优化或随机优化
确定性优化最简单:把光伏、负荷都当成已知值,直接求一个最优解。问题是,现实里的光伏出力、负荷需求、电价波动,没有一天是能精确预测的。确定性优化的结果往往在边界处非常脆弱,比如储能容量刚好够用,置信度高的预测下没问题,实际来一个多云天气,光伏骤降,整个方案直接失效。
随机优化用场景集来刻画不确定性,如果场景概率分布真实,结果确实更稳。但随机优化的计算量很大,而且最怕一件事:概率分布本身估计不准确,那你生成再多场景也是对着错误的分布做优化。
两阶段鲁棒优化走的是另一条路。它不假设不确定性服从什么具体分布,只给定一个不确定集合,要求最坏情况下也能满足约束、保住经济性。第一阶段做投资决策,这属于“现在就要拍板”的事;第二阶段等不确定性实现后做运行调度,是“见招拆招”的事。两阶段鲁棒优化就是先假设不确定性“故意”往最坏方向走,然后在这个最坏场景下让系统运行成本尽可能低。
用一句话概括:两阶段鲁棒优化是在“投资决策—运行调度”两层结构上,把不确定性敌人当作一个会在集合内任意取值的对手,找到能抵抗最坏情况的方案。
1.3 这个方案能带来什么实际收益,适用场景在哪里
我在模拟项目X里做过对比:同一组微网参数,确定性优化配置的储能容量比两阶段鲁棒优化小大概18%,但整个调度周期的缺电惩罚成本高出约23%。把投资成本和运行成本加在一起,鲁棒方案的总成本反而比确定性方案低了不少。也就是说,表面上看鲁棒优化“多建了设备”,实际上它用前期可控的增量投资,换来了运行阶段更低的失负荷风险和更高的供电可靠性。
这套方法最适用的场景是:光伏渗透率高、负荷波动大的微网群项目。比如某园区三个微网通过一条公共母线互联,每it微网都有分布式光伏和储能,还有燃气轮机作为可调度电源。这种结构下,气象波动对每个微网的影响并不完全一致,联合调度就能利用“东边多云、西边晴朗”的空间互补性来对冲风险,而两阶段鲁棒优化正好能把这个对冲效果建模进最坏情况里。
2. 两阶段鲁棒优化算法核心原理与模型构建
2.1 从确定性优化到两阶段鲁棒的模型升级路径
先看确定性模型长什么样。目标函数通常是投资成本加运行成本:
min C_inv(x) + Σ_t C_op(x, y_t)
其中x是容量决策变量,y_t是每个时段的运行决策变量。约束包括功率平衡、机组出力上下限、储能SOC约束、联络线传输功率约束等等。
两阶段鲁棒优化把这个模型拆成两层。第一阶段只关注x,也就是光伏装机容量、储能额定容量/额定功率、柴油机容量等投资变量。第二阶段的运行决策y_t依赖于不确定性参数u_t(比如光伏实际出力、负荷实际功率)的实现,所以运行成本写作:
min_x { C_inv(x) + max_u∈U min_y_{t} Σ_t C_op(y_t, u_t) }
这个max-min结构就是两阶段鲁棒的核心。max部分代表未知的“自然”在选择最坏情况,min部分是调度中心在给定最坏情况下做最优运行决策。求解这个结构,常用的方法就是C&CG(列与约束生成)算法,把主问题(投资决策)和子问题(最坏场景下的运行调度)交替迭代求解。
2.2 不确定集合怎么设计才合理
不确定集合是两阶段鲁棒优化的灵魂。工程上最常用的是盒式集合和预算约束集合。
盒式集合最简单:
U = { u_t : u_t,min ≤ u_t ≤ u_t,max }
它表示每个时段的不确定性参数都在一个上下界范围内波动,但完全不限制同一时段多个参数之间的相关性,也不限制时间上的连续性,容易导致最坏情况过于保守。比如光伏出力,允许所有14个小时都同时取最小值,这在现实中几乎不可能,但模型会按这种极端情况做设计。
预算约束集合就是在盒式集合基础上,限制整体偏移程度不超过某个预算Γ:
U = { u_t : u_t,min ≤ u_t ≤ u_t,max, Σ_t |u_t - u_t,ref| / (u_t,max - u_t,min) ≤ Γ }
Γ可以理解成“坏运气上限”。Γ越大,模型越保守;Γ=0时退化为确定性模型;Γ取到最大时等价于盒式集合。我实际调试时,通常先跑Γ=0的基线方案,然后逐步增大Γ,观察总成本和设备容量配置的变化趋势到底对不对,这样容易发现模型错误。
2.3 为什么说“联合调度”和“容量配置”耦合是难点
容量配置是年/月级别的慢决策,运行调度是小时级别的快决策。两阶段鲁棒优化把两种时间尺度放在同一个模型里,难点在于第二阶段运行约束必须包含每个微网的设备模型、网络约束、以及微网之间的交互功率约束。如果不做联合调度,每个微网单独配置容量再单独调度,那么最坏情况下微网A缺电时不能指望微网B帮忙,配置结果必然偏保守;如果做联合调度,第二阶段就必须在同一个优化问题中求解所有微网的运行变量,变量维度急剧膨胀。
我记得第一次建模时,三个微网,每个微网96个时段,光第二阶段运行变量就有上万个。MATLAB里直接暴力写约束矩阵,内存占用高得惊人,调试起来还特别难定位是哪条约束出了问题。后来我改用约束函数化处理,把每个微网的约束封装成独立的函数,主迭代流程只调用函数接口,问题定位效率高了很多。
2.4 求解算法:C&CG迭代框架怎么落地
C&CG的核心思路其实不复杂。把原问题拆成主问题MP和子问题SP:
- MP:给定一组从子问题中提取出的最坏场景u*,求解投资决策和运行决策,得到一个下界LB以及一阶段变量x。
- SP:固定x,求解max_u min_y的问题,得到最坏场景u*和上界UB。
- 如果UB与LB之间的间隙小于阈值,说明收敛;否则把u*作为新场景加入MP,继续迭代。
看起来简单,但落地时有很多细节。比如SP本身是个max-min问题,没法直接用求解器解。常规做法是利用强对偶定理把内层min问题对偶成max问题,这样SP就变成一个单层max问题;或者使用KKT条件、线性化互补松弛条件来转化。对偶变换里面很容易出错,尤其要注意变量符号、约束方向和对偶变量取值范围。我自己更习惯用对偶变换,因为对偶后在MATLAB里整合成大矩阵比较规整。
3. 容量配置与调度耦合的实现细节
3.1 第一阶段投资决策变量与目标函数拆解
第一阶段涉及的容量决策主要包括:
- 光伏装机容量P_pv,i,单位kW,投资成本按单位容量成本乘以容量计算。
- 储能额定能量E_ess,i和额定功率P_ess,i,成本通常分为能量成本和功率成本两部分。
- 燃气轮机(或柴油机)额定功率P_dg,i,以及线路扩容容量P_line,ij(如果允许扩容)。
目标函数中投资成本我习惯写成:
C_inv = Σ_i [ c_pv P_pv,i + c_ess,e E_ess,i + c_ess,p P_ess,i + c_dg P_dg,i ] + Σ_{(i,j)} c_line P_line,ij
其中c_pv、c_ess,e等是单位容量成本系数。注意,第一阶段变量不依赖时段,变量量级比运行阶段小很多,但它们直接决定了第二阶段可行域的大小,因此第二阶段约束里必须把第一阶段变量作为参数或变量关联进去。在MATLAB的yalmip中,我通常直接把x和y都定义为sdpvar,然后统一建模,这样主问题里的割平面可以更方便地加入。
3.2 第二阶段运行约束怎么映射到各个微网
第二阶段运行约束分成几大类。
第一类是每个微网内部的功率平衡约束。对微网i,在时段t:
P_pv,i,t + P_dg,i,t + P_ess,i,t_dis - P_ess,i,t_ch + Σ_j P_line,ij,t = P_load,i,t
需要说明的是,鲁棒优化里P_pv,i,t不是固定值,它等于u*_pv,i,t乘以装机容量P_pv,i;负荷也可能是不确定参数,所以P_load,i,t可以写成u*_load,i,t。如果你只考虑光伏不确定性,负荷保持预测值,模型也会简化很多。工程上建议先做单参数不确定,跑通后再扩展到多参数不确定。
第二类是设备运行约束。燃气轮机出力在0到额定功率之间,且有爬坡约束;储能充放电功率限制、SOC递推公式和SOC上下限;每个时段的充放电状态互斥,需要引入二进制变量。
第三类是微网间的联络线约束。联络线功率通常在[-P_line,max, P_line,max]范围内,如果允许双向传输,传输损耗可以忽略或按系数折算。联合调度的核心价值在这一类约束上体现:微网之间可以共享备用容量,最坏情况下不需要每个微网都自带巨大储能。
3.3 最坏不确定场景的提取与割平面生成
第二阶段子问题求解完成后,最重要的工作是提取使目标函数最大的那组u*。在C&CG中,主问题每轮需要加入一组新的场景约束,核心代码结构大致如下:
for iter = 1:max_iters % 求解主问题MP optimize(MP_constraints, MP_obj, MP_options); LB = value(MP_obj); x_current = value(x); % 基于x_current求解子问题SP [u_star, SP_obj] = solve_SP(x_current); % 更新上界,注意SP_obj是max-min问题的值,需要加上投资成本才是总上界 UB = min(UB, value(C_inv) + SP_obj); if (UB - LB)/UB < tol break; end % 将u_star作为新场景加入主问题,并增加对应运行变量副本 add_new_scenario_to_MP(u_star); end这段伪代码看起来简单,但有个关键点经常被忽略:子问题求出来的SP_obj只是运行成本,而主问题的LB包含了投资成本。如果你的UB是“投资成本+运行成本”,那每次迭代必须保持口径一致。我第一次实现时就因为口径不一致,导致UB和LB永远不可能收敛,查了半天才意识到是忘记把投资成本加到UB上。
3.4 MATLAB中的两阶段问题建模技巧与求解器配置
MATLAB里我推荐直接用yalmip写模型。它支持用sdpvar定义变量,用constraint定义约束,并且能方便地嵌入C&CG迭代。求解器方面,如果模型是线性规划或混合整数线性规划,选gurobi效果最好;没有商业求解器的话,可以先用默认的linprog/intlinprog把整个流程跑通,再换求解器验证结果一致性。
我实际建模时会把主问题和子问题分别写成两个函数,例如:
function [LB, x_val, scenario_list] = solve_MP(scenario_list, params) % 使用yalmip定义变量和约束,包含已有场景的运行变量 end function [SP_obj, u_star] = solve_SP(x_val, params) % 内层min通过对偶变换,外层max直接求解 end这样调试时可以把主问题、子问题分开测试。子问题尤其要单独验证:固定任意一组x,求解SP,然后手动检查得到的u_star是否真的在不确定集合内,目标值是否合理。
4. 关键代码实现与仿真流程
4.1 一个可运行的最小示例框架(单微网到多微网扩展)
为了让你快速上手,我先给一个单微网的两阶段鲁棒优化最小框架。单微网的参数:光伏不确定出力、储能、燃气轮机、负荷。由于是单微网,第二阶段只有内部约束。扩展成多微网时,只需增加联络线功率变量和联络线约束。
% 最小两阶段鲁棒优化框架(单微网) T = 24; % 第一阶段变量 P_pv_rated = sdpvar(1,1); E_ess_rated = sdpvar(1,1); P_ess_rated = sdpvar(1,1); P_dg_rated = sdpvar(1,1); % 第二阶段变量(第一批场景,后续迭代增加) function scenario_vars = create_scenario_vars(T) P_dg = sdpvar(T,1); P_ch = sdpvar(T,1); P_dis = sdpvar(T,1); SOC = sdpvar(T,1); s_ch = binvar(T,1); % 储能充电状态 s_dis = binvar(T,1); % 储能放电状态 scenario_vars = struct('P_dg',P_dg,'P_ch',P_ch,'P_dis',P_dis,... 'SOC',SOC,'s_ch',s_ch,'s_dis',s_dis); end由于篇幅原因,我不能把全套代码贴完,但这个框架很直观:第一阶段变量放在最外层,第二阶段变量按场景分组,每个场景对应一组运行变量副本。多微网扩展只需要给每个微网创建各自的变量,再多加一个联络线功率变量矩阵。
4.2 光伏出力与负荷不确定集合的参数生成
实际仿真中,光伏出力的不确定范围往往来自历史数据统计。比如某微网光伏出力预测值和实际值的偏差服从某个统计规律,你可以把每个时段的预测值乘以一个比例系数得到上下界。更严格的做法是:
% 假设pv_forecast是预测归一化出力(0~1) alpha = 0.15; % 上下浮动比例 u_pv_min = max(0, pv_forecast - alpha); u_pv_max = min(1, pv_forecast + alpha); % 不确定集合参数:u_pv_min、u_pv_max、预算Gamma预算Γ怎么选?通常做成一个可调参数,先设Γ=0得到参考解,然后扫描Γ从0到T(24),观察总成本和容量配置变化曲线。如果曲线单调且合理,说明模型正确;如果曲线出现非单调或跳变,优先检查不确定集合的约束写法是否正确,尤其是绝对值线性化部分。
4.3 仿真流程与结果分析要点
跑完整仿真后,建议至少输出三类结果:
- 容量配置结果对比表格:不同Γ下的光伏、储能、燃气轮机容量。
- 最坏场景下的运行调度曲线:画出某一次迭代得到的u_star对应的光伏出力、SOC变化、机组出力、微网间交互功率。
- 收敛曲线:LB、UB随迭代次数的变化。
收敛曲线是最容易发现问题的。正常情况下LB单调递增,UB单调递减,最终收敛在一个间隙内。如果你发现LB出现下降,说明主问题里的割平面约束或投资成本项写错了;如果UB不降反升,说明子问题求解结果不稳定,极可能是对偶变换时漏了约束或漏了变量。
4.4 多微网联合调度的算例组织思路
算例方面,我建议这样设计:三个微网,每个微网接一组光伏和一个储能。微网1负荷高、光伏一般;微网2光伏很好、负荷一般;微网3负荷和光伏都居中。三个微网呈辐射状接入公共母线。
这样设计的好处是:最坏情况下,光伏波动主要影响微网2,但微网2的多余容量可以通过联络线支援微网1,联合调度优势特别明显。你可以设置两种对比方案:
- 方案A:三个微网独立调度,不允许功率交换。
- 方案B:三微网联合调度,允许通过母线交换功率。
然后分别用两阶段鲁棒优化算法求解容量配置,对比总成本、储能总量、切负荷量。我实测下来,方案B的储能总容量可以减少20%~30%,而且最坏场景下的失负荷率显著更低。
5. 调试实战:典型问题与排查技巧实录
5.1 子问题对偶变换时最容易踩的坑
两阶段鲁棒优化的子问题是最容易写错的地方。max-min问题转成单层max问题时,内层min问题要先写成标准形式:min c' z, s.t. A z ≤ b, z ≥ 0。如果你的模型里有等式约束,要拆成两个不等式约束,或者明确引入自由对偶变量。对偶时变量符号极其容易搞反。
我给自己的调试流程是:先固定一个很小的实例,比如T=6,且x取某组已知值,手动枚举几个u值,把内层min问题的值算出来,然后和子问题求解器输出的评价函数对比。如果对上了,再做外层max。这样逐层验证,能大大缩小排查范围。
还有一点:对偶后的子问题目标函数里会出现x·u相乘的项。如果你的x在第一阶段已经固定为数值,那没问题,直接变成线性项;但如果x在主问题里是sdpvar,你需要在每个割平面生成后固定x数值,再构建子问题。不能把符号变量传进子问题,否则会出现双线性项,求解器会报非线性错误。
5.2 收敛缓慢或震荡的原因排查
C&CG算法理论上是有限收敛的,但实际中可能出现迭代几十轮还不收敛的情况。我遇到过的最常见原因是主问题规模膨胀后,数值稳定性变差。特别是储能SOC递推约束,SOC的初值或末值约束处理不当,会导致子问题目标值随迭代产生波动。
另一个常见原因是预算约束的绝对值线性化。如果你用yalmip自带的abs函数,它可能引入额外变量导致约束形式不理想。更稳妥的方式是自己手动引入辅助变量w,写成:
w ≥ u - u_ref w ≥ -(u - u_ref) Σ_t w_t / (u_max - u_min) ≤ Γ
这样线性化是完全可懂的,排查也容易。
如果真的遇到震荡,建议先调小Γ,再检查每一轮加进主问题的场景约束是否正确包含了所有运行变量。加场景时,经常有人漏掉状态变量(SOC)的后续递推耦合,导致主问题新增场景可abandoned其他场景约束。
5.3 约束不可行时的定位技巧
如果求解器报“infeasible”,不要直接怀疑模型复杂,先做这几件事:
- 检查第一阶段变量的非负约束是否都写了。容量变量必须是实数非负,但yalmip默认变量可以是负的,忘记加下限会导致储能容量为负这种荒唐解。
- 检查第二阶段变量的初始约束。比如储能SOC(1) = 0.5 E_ess,这个约束在主问题每新增一个场景都要重复加入;如果你在构造函数时没有按场景实例化,很容易漏掉。
- 检查联络线功率平衡约束的正负号。多微网场景下,微网间的功率交换方向很容易搞反。我建议在功率平衡约束里统一采用“流出为正”,联络线功率变量对所有微网都按同一方向定义,避免出现双向变量。
5.4 大场景变量维度高导致内存溢出的应对
三个微网、96时段、两个不确定参数,子问题变量规模已经不小。如果再加多个场景进主问题,内存占用会逐步上升。我的经验是采用“双线性化+矩阵稀疏化”策略:yalmip构建的约束尽量用向量化写法,不要用for循环逐条添加单个约束。否则约束数量一样,但表达式树的开销差异很大。
如果还是内存吃紧,可以考虑只把最坏场景的最后一次迭代结果保留,不保存所有中间场景变量。不过你要记得,C&CG主问题需要“所有已发现场景”都作为约束副本存在,所以不能省太多。折中方案是设定最大迭代次数,比如8~12轮,通常已经能得到工程上满意的间隙。
6. 经验总结与扩展建议
两阶段鲁棒优化的多微网联合调度与容量配置,不是那种“套个工具箱就能出结果”的问题,它对模型理解的深度要求很高。我个人的体会是:先把第二阶段确定性运行调度模型做扎实,能正确求解、结果符合物理直觉,再上两阶段鲁棒迭代。很多新手一上来就引入不确定集合,结果模型错误被不确定性放大,根本找不到bug。
调试期间还有一个小技巧非常管用:把Γ设成0,此时两阶段鲁棒优化应该退化为确定性优化。跑出来的容量配置结果应该和确定性模型完全一致。如果不一致,说明模型里某些条件在鲁棒性处理时被错误改动,优先检查不确定集合约束是否在Γ=0时完全松弛,以及场景副本的构建逻辑。
这个项目后续还能扩展的方向挺多。比如引入电动汽车充放电需求响应、考虑储能寿命衰减成本、把碳排放约束加进目标函数做成低碳鲁棒调度,或者改成分布式鲁棒优化,用均值方差信息构造不确定集合。这些扩展都建立在两阶段鲁棒结构和容量配置耦合框架上,基础模型没走偏,后续扩展就是水到渠成的事。
最后再分享一个我自己常用的验证思路:用历史某天的实测数据把不确定性参数区间缩小到几乎为0,得到的调度结果应该接近真实场景下的最优调度。如果这一步对不上,说明运行模型本身有问题,就别急着去研究算法层面的收敛性了。基础模型稳了,鲁棒优化的大树才能长得起来。