
没接触过微电网调度的人可能觉得“鲁棒优化”就是给风电光伏出力加一个不确定区间然后求解一个min-max问题。真正上手复现过含储能的多阶段鲁棒调度模型的人应该都体会过那种“模型看着不难一跑就崩跑了不收敛收敛了结果又离谱”的崩溃感。这篇博客想把一套含可再生能源和储能的区域微电网多阶段鲁棒调度模型的完整复现过程记录下来——从问题建模、MATLAB和YALMIP下的代码骨架、CCG求解流程到各种匪夷所思的坑和排查方法尽量给正在做微电网最优运行、鲁棒优化或者储能调度的朋友一份能少走弯路的参考。1. 为什么说“多阶段”才是含储能微电网鲁棒调度的正解1.1 两阶段鲁棒在储能面前的先天不足最早接触微电网鲁棒调度时大部分文献模板都是两阶段鲁棒优化第一阶段做日前机组组合和储能充放电计划第二阶段在风电、光伏出力不确定的现实下调整机组出力。这个思路用在纯火电或者纯燃气轮机的系统里没什么问题但一旦加入储能就会遇到一个很尴尬的情况——储能SOC是跨时段强耦合的变量第一阶段的充放电计划一旦定了第二阶段即使发现可再生能源出力偏差很大也只能在很小的范围内修正储能这个最灵活的资源反而被“锁死”了。为什么因为两阶段模型里所有非此刻决策的变量都打包到第一阶段而所有“看到不确定性后还能调整”的变量全部塞到第二阶段。含储能时一个小时的充放电功率会影响后面十几个小时的SOC相当于把整个时间解耦链条砍断了。要么第一阶段保守到储能几乎不敢动作要么第二阶段出现大量切负荷或弃风弃光。这也是许多人在复现“含储能微电网鲁棒调度”时明明模型公式都对结果却远差于预期的主要原因。1.2 多阶段鲁棒到底在优化什么多阶段鲁棒优化的核心不再是简单的min-max而是一个min-max-min-...-min的序贯决策问题。可以这么理解系统在每个时段开始时只知道当前及未来可再生能源出力的不确定区间但不知道实际值只能根据已经实现的过去出力来做当前决策然后下一时段再根据新信息修正。这种“滚动决策”的特性天然契合储能SOC的递推约束因为每个时段的储能状态都由上一个时段的状态和决策决定而不是一次性把所有时段的SOC都固定下来。在数学形式上多阶段鲁棒通常通过决策规则来近似最常见的是仿射决策规则affine decision rule即把当前时段的决策变量表示成已实现不确定量的仿射函数。这样做的好处是把一个无穷维的决策问题降维成一个有限维线性规划或者混合整数规划坏处是仿射限制会带来一定的保守性。但实际复现中对微电网这种规模的问题仿射决策规则的保守性完全可以接受尤其是和储能配合时效果比两阶段模型好太多。另外需要说明一点标题中的“完全复现”并不是指我凭空造一套模型而是指将一个公开发表的微电网调度文献中的模型用MATLAB从零到一实现出来并跑出和原文趋势一致的结果。复现过程中最大的工作量并不在编程而在处理“模型假设”和“代码实现”之间的无数工程细节后面我会逐个展开。2. 区域微电网多阶段鲁棒调度模型目标、约束与耦合关系2.1 目标函数里该放哪些成本区域微电网的调度目标通常是总运行成本最小化。我复现的模型包含三类成本第一类是常规机组微型燃气轮机、柴油机的发电成本一般用二次函数表示工程上会分段线性化处理第二类是储能充放电造成的退化成本虽然有些文献忽略但加上之后可以避免储能过度频繁充放结果更符合实际第三类是失负荷惩罚成本用来保证模型在极端场景下不会为了压成本而牺牲可靠性。目标函数写成多阶段鲁棒形式后大概是这样的叠加关系外层是对第一阶段决策比如机组启停状态的成本求和内层是在每一阶段对已实现不确定性条件下的运行成本做最优决策。这个过程中需要特别注意成本函数中的绝对值项比如储能充放电切换惩罚、备用调用惩罚等建议直接引入辅助变量线性化不要指望求解器能高效处理非线性成本。2.2 约束清单功率平衡、机组爬坡与储能SOC递推模型约束大致分成四组。第一组是节点功率平衡约束即每个时段的发电总功率加上储能放电功率等于负荷功率加上储能充电功率如果网络拓扑不可忽略还要加上支路潮流约束。我复现的算例是单母线的区域微电网所以只需整体功率平衡。第二组是常规机组约束包括出力上下限、爬坡约束以及最小启停时间约束。爬坡约束在多阶段鲁棒问题里很有讲究机组出力本身可以作为第二阶段决策爬坡约束必须考虑相邻时段的不确定性相关性写仿射决策规则时要仔细展开。很多人复现时把爬坡约束直接写成静态上限这是不对的因为上一时段的出力可能已经是最恶劣场景下的值下一时段可调整范围会被压缩。第三组是储能约束包括充放电功率上下限、储能SOC递推关系、SOC上下限以及充放电互斥约束。SOC递推是多阶段问题中最关键的耦合约束代码实现时我习惯写成连续变量并用式(1)的递推形式而不是用一大堆二元变量去枚举每个时段的状态否则求解规模会爆炸。[ SOC_{t1}SOC_t\eta_c P_{c,t}\Delta t-\frac{1}{\eta_d}P_{d,t}\Delta t ]其中充放电互斥约束 (P_{c,t}\cdot P_{d,t}\le0) 是非线性的实际建模用二元变量或者一个大M法处理。注意大M的取值不能太大我在调试中发现M取1000会产生数值问题改成100左右后求解稳定性明显提升。第四组是鲁棒可行性约束用来保证对于不确定集合内的任意风光出力场景都存在第二阶段决策使系统不切负荷或者切负荷在允许范围内。严格形式会引入max-min子问题这也是CCG算法要处理的环节。2.3 鲁棒可调变量的映射哪个决策等不确定性实现后再定多阶段模型里最绕人的就是变量分类。该问题中“启停状态”是典型的第一阶段决策因为机组启停需要提前一天决定无法等看到实际风光再调整而“机组出力”“储能充放电功率”“购售电功率”是第二阶段或第t阶段决策它们可以等当前及历史不确定性实现后再做调整。我复现时选择把所有阶段的可调变量都写成仿射决策规则[ u_tu_t^0\sum_{k\le t} U_{t,k},\omega_k ]这里 (\omega_k) 是第k个时段的可再生能源出力偏差。需要注意求和只对 (k\le t) 进行因为第t阶段的决策不能预知未来不确定性否则就违反了非预期性约束。很多初学的朋友在这里会顺手写成对所有k求和模型立刻退化成“先知”模式得到的结果过于乐观完全失去鲁棒意义。3. MATLABYALMIP复现核心建模代码骨架与CCG求解流程3.1 不确定集的YALMIP表达建模前先定义可再生能源的不确定集合。我采用经典的盒式不确定集加预算约束% 预测出力向量 p_wind_fc [30, 28, 26, ...]; % 各时段风电预测值 p_pv_fc [0, 0, 5, ...]; % 各时段光伏预测值 delta_max 0.2; % 预测偏差比例 Gamma 6; % 预算约束限制总偏差时段数 sdpvar w(T); % 风电出力不确定性 sdpvar v(T); % 光伏出力不确定性 % 不确定集出力在[预测*(1-delta_max), 预测*(1delta_max)]内 % 且累计偏差不超过Gamma unc_con [ ... (1-delta_max)*p_wind_fc w (1delta_max)*p_wind_fc; (1-delta_max)*p_pv_fc v (1delta_max)*p_pv_fc; norm([w-p_wind_fc; v-p_pv_fc], 1) Gamma ... ];这里用1范数约束区间内总偏差比单纯盒式集合更接近实际因为极端情况不可能所有时段同时发生。Gamma的取值很关键我后面会专门讲调参经验。3.2 主问题与子问题交替迭代的代码逻辑CCG算法的思路是把多阶段鲁棒问题拆成主问题MP和子问题SP通过不断迭代扩充主问题约束来逼近最优解。伪代码逻辑如下% 初始化 LB -inf; UB inf; k 1; worst_scenario ones(T, 2); % 初始最恶劣场景 max_iter 20; tol 1e-3; while (UB - LB) / abs(UB) tol k max_iter % 1. 求解主问题MP给定当前worst_scenario得到一组决策 [sol_MP, obj_MP] solve_MP(worst_scenario); LB max(LB, obj_MP); % 2. 固定第一阶段决策求解子问题SP寻找最恶劣场景 [sol_SP, obj_SP, worst_scenario] solve_SP(sol_MP); UB min(UB, obj_SP); % 3. 若gap过大向主问题添加对应于worst_scenario的新割平面 if (UB - LB) / abs(UB) tol add_cut_to_MP(worst_scenario); end k k 1; end主问题是在给定有限个最恶劣场景下求最小运行成本子问题是固定第一阶段决策后求最恶劣不确定性场景下的最大运行成本。理论上子问题是一个max-min问题需要通过对偶变换或KKT条件转成单层最大化问题。3.3 从主问题到MP、SP的割平面传递主问题初始只有一组不确定参数随着迭代逐渐加入极端场景对应的约束所以MP规模会越来越大。我复现时遇到一个典型问题如果子问题返回的worst_scenario每次都变化很小收敛就很慢。后来发现原因是主问题第一阶段决策机组启停在很少的迭代次数内就会锁定但储能SOC的初值每次迭代都在跳变导致子问题场景来回抖。解决办法是给主问题增加“储能递推约束在已添加到MP的所有场景下都必须满足”的强制条件并且在子问题求解时固定储能初值。另外CCG有两种主流变体一种是只添加子问题最优解对应的约束另一种是同时添加子问题对偶乘子对应的约束。后者收敛更快但代码上要维护对偶乘子数组我建议先跑通前者再考虑加速。以下是主问题核心建模我用YALMIP的constraint对象来组织% 主问题决策变量 sdpvar p_g(T); % 机组出力 binary z_g(T); % 机组启停 sdpvar p_c(T); p_d(T); % 储能充放电 sdpvar soc(T); % SOC sdpvar shed(T); % 失负荷 MP_cons []; MP_cons [MP_cons, ... % 功率平衡每个已列出的场景下都要满足 % 这里k从1到max_iter循环添加约束集 ]; % 目标函数运行成本 启停成本 失负荷惩罚 objective_MP sum(a_g * p_g.^2 b_g * p_g) ... sum(C_start * max(0, diff(z_g))) ... C_shed * sum(shed);实际代码里需要将已找到的worst_scenario矩阵传入MP并在功率平衡约束中用这些场景数据替换不确定参数。这里有个小技巧——不要把场景矩阵直接硬编码进约束而是用cell数组或者三维矩阵保存历史场景否则循环到后面代码会非常凌乱。4. 复现路上的大坑求解失败、慢收敛与结果不合理4.1 强对偶转写的双线性项处理子问题是max-min结构最常用的方法是把内层min问题用强对偶写成max形式这样就得到一个max问题。这中间会出现一个典型的双线性项不确定变量和原问题对偶乘子的乘积。比如子问题中功率平衡约束对应的对偶乘子为 (\lambda_t)而功率平衡右边含有不确定性 (\omega_t)于是目标函数出现 (\lambda_t \omega_t) 的乘积项。由于 (\lambda_t) 和 (\omega_t) 都是优化变量且互相独立乘积是非凸的。常见的处理方法是利用不确定集合的极点性质——最优场景一定出现在盒式集合的顶点上。这样 (\omega_t) 可以离散化为“取上限”或“取下限”再通过大M法线性化双线性项。我在模型里加入了预算约束后最恶劣场景不再是简单所有时段都取极端值而是一个“在有限的Gamma个时段内取极端偏差”的组合问题因此还需要额外的0-1变量来指示哪些时段可以激活极端偏差。这段代码很容易写错。我的经验是不要自己硬写大M线性化公式而是直接用YALMIP的implies或者binvar配合norm来建模让求解器自动处理。比如对于“如果激活极端偏差则对偶乘子不超过某个上界”这类逻辑用implies(activate, lambda M)比手工写大M约束更安全数值稳定性也更好。4.2 SOC初值、终值与时段耦合的守恒性储能SOC初值和终值处理不当会让多阶段模型产生非常诡异的振荡。如果你把SOC初值设为0终值不加约束优化器会倾向于在最后几个时段把储能放空因为剩余能量没有价值。这虽然满足模型却不是我们想要的运行方式。我在复现里给SOC终值加了一个范围约束要求调度周期结束时SOC接近初始值例如百分之五十并保留一个松紧系数。这样模型就必须“瞻前顾后”不会只顾头不顾尾。更精细的做法是在目标函数里给SOC终值加上一个价值项相当于剩余的储能能量按一定价格回收这样模型会自动权衡当前用电和未来备用。还有一个容易被忽略的细节SOC递推中的充放电效率 (\eta_c) 和 (\eta_d) 不应相等。很多简化模型直接设成0.9但真实的锂电池充放电效率差异会导致SOC漂移我建议设为0.95和0.92这种有区分度的值并且在子问题里也要严格保留这个递推关系否则主问题和子问题对SOC的理解不一致迭代会很难收敛。4.3 收敛判据与步长选择CCG的收敛判据直接影响复现结果的可信度。我见过不少人直接把UB - LB小于某个绝对误差就停但目标函数动辄几万块绝对误差取0.01几乎不可能达到。应该用相对误差例如(UB - LB) / UB 1e-3。此外多阶段模型随着时段数增加CCG的迭代次数可能超过50次。我在24时段、6节点算例中通常需要10~15次迭代收敛但一旦把Gamma调到10以上迭代次数会急剧增长。这里就需要权衡要么降低Gamma值要么在子问题里限制最恶劣场景的总偏差次数。理论上讲Gamma越大越保守结果越“安全”但成本也越高。实际操作时我建议先跑Gamma0和Gamma4两组观察成本变化率再决定要不要继续调大。代码上还有一个非常实用的经验在每次迭代结束后打印LB、UB和当前最恶劣场景的前几个时段出力如果发现LB或UB单调性被破坏说明主问题或子问题的约束写错了而不是算法问题。CCG本身保证LB单调不减、UB单调不增一旦违反99%是约束传递时用了错误的场景索引或者漏了某条割平面。5. 算例验证从成本曲线到最恶劣场景出力5.1 6节点微电网算例设置为了验证复现效果我没有直接照搬文献里的算例而是设计了一个标准的6节点低压微电网测试系统包含一台微型燃气轮机额定功率1.5 MW、一台柴油机1 MW、一组0.8 MWh储能最大充放电功率0.25 MW、一座光伏电站预测峰值1.2 MW和一座风电场预测峰值0.5 MW。负荷曲线采用某区域电网的典型日负荷最大负荷2.4 MW。时段分辨率取1小时共24个调度时段。不确定集参数设置如下风电预测偏差20%光伏预测偏差15%预算约束Gamma6。储能SOC初值和终值都设为0.5。所有经济参数按常规微电网文献取失负荷惩罚设为10000元/MWh储能退化成本设为50元/MWh。5.2 鲁棒解与确定性解的对比先说确定性最优调度结果总运行成本约8.6万元储能基本按照“低谷充电、高峰放电”的模式运行光伏大发时段会出现一部分弃光。换成多阶段鲁棒模型后总成本上升到9.8万元增幅约14%这是因为模型必须为最恶劣天气预留更多爬坡和备用容量。最恶劣场景下的出力曲线很有意思光伏出力在中午时段被压到预测值的下限风电在凌晨时段出现连续低出力这时候柴油机被顶满储能从早高峰就开始放电到下午SOC已经低于0.2。这个结果直观展示了“鲁棒性”的代价和意义确定性模型在晴天场景下运行得很漂亮但一遇到连续阴天加静风的场景就可能大量切负荷而鲁棒模型通过多储备一部分昂贵出力和更保守的SOC管理换来了在最坏情况下的可靠性。在CCG迭代过程中我记录了LB和UB的变化。第一次迭代后LB为9.2万元UB高达11.5万元经过6次迭代gap缩小到5%第11次迭代后gap为0.2%收敛。这个收敛速度比两阶段鲁棒慢但比完整的动态规划枚举要快得多。6. 基于这个框架还能怎么扩展6.1 加入需求响应和可中断负荷多阶段鲁棒框架天然适合加入需求响应。可以把可中断负荷建模成一种特殊的“负储能”在不确定场景最恶劣时切除部分非关键负荷并支付补偿费用。只需要在不确定集和功率平衡中加入需求侧的决策变量同时用仿射决策规则表示响应量。我后续做的一个扩展版本里把原算例中的固定负荷改成20%可响应负荷结果总成本从9.8万元降到9.1万元而且最恶劣场景下的失负荷量下降了将近一半。这说明需求响应能有效缓解鲁棒调度中的保守性代价是额外引入响应不确定性需要多一层鲁棒约束来保证响应能力本身不会成为新的风险点。6.2 多微网互联与碳约束单区域微电网模型扩展成多微网互联时每个微网都有自己的储能和可再生能源同时通过公共联络线交换功率。这时的多阶段鲁棒模型会变得非常大我建议不要把所有微网揉在一个模型里求解而是采用分布式算法比如交替方向乘子法或者Benders分解每个微网内部仍然沿用本文的CCG框架微网之间只交换联络线功率信息。另外如果考虑碳排放约束可以在目标函数中加入碳成本项或者在约束中加入碳排放上限。因为碳成本和发电功率强相关而且储能可以通过转移负荷来降低高碳机组的出力所以和鲁棒优化结合后模型能自动在“降低碳排放”和“抵抗不确定性”之间找到平衡。我在某个课题里试过把碳价格设为200元/吨结果储能放电时段明显增多这是因为鲁棒模型愿意牺牲一部分成本来换取低碳运行。最后再分享一个我复现时的习惯拿到一个多阶段鲁棒调度模型第一件事不是查公式而是先在纸上画出变量在时间和不确定性两个维度上的关系图搞清楚哪些变量是“看到不确定之前就要定”的、哪些是“看到之后才能定”的。这个问题想清楚了后面的建模、写代码、调bug都会顺畅很多。CCG跑通之后也不要急着当黑盒用花一个下午把主子问题的约束逐条打印出来对比往往能发现一些你完全没想到的小错误。这些错误不会让模型跑崩但会让结果偏离真实最优解百分之十几恰恰是最难排查的。