
复现微电网两阶段鲁棒优化经济调度听起来像一个典型的学术论文复现任务但真正动手做过的人都知道论文里寥寥几行公式背后藏着大量“写不出来”的坑对偶问题怎么构造、KKT条件怎么处理、CCG主问题和子问题怎么迭代、不确定性预算怎么设置还有最让人头疼的——为什么我跑出来的结果跟论文对不上。这篇文章把我从零开始复现“两阶段鲁棒优化经济调度”的完整过程记录下来包括数学模型怎么建、代码架构怎么搭、参数怎么调、坑怎么踩以及最后怎么一点点把结果调试到与论文吻合。内容会比较长但每一个环节都是实际跑通的适合正在做微电网优化调度、鲁棒优化入门、或者被CCG算法折磨的同行参考。1. 复现前的准备先把模型和算法吃透1.1 为什么选两阶段鲁棒优化而不是传统随机规划微电网的经济调度本质上是一个“现在做决策、未来看结果”的问题。今天决定每台机组出多少力、储能充多少电但明天风电光伏到底发多少电、负荷有多高没人能提前知道。传统做法有两种确定性优化和随机规划。确定性优化最简单直接把风电光伏预测值当成确定值来算速度快、实现容易但结果往往过于乐观——一旦实际出力偏离预测值调度方案就可能不满足约束甚至出现失负荷。随机规划则给每个场景分配一个概率追求期望成本最小。听起来更合理但它有两个硬伤一是需要提前假设概率分布实际中很难准确给出二是场景数量一大求解规模爆炸计算时间让人等不起。两阶段鲁棒优化走的是另一条路我不猜概率我只定义一个不确定性集合把可能出现的风光出力都框在这个集合里然后寻找一个在最坏情况下仍然可行的调度方案。这种思路特别适合微电网这种“安全第一、经济第二”的场景——你可以接受不是最便宜的方案但不能接受某个极端天气下直接崩掉。1.2 CCG算法的核心逻辑主问题与子问题的博弈两阶段鲁棒优化最常用的求解方法是列与约束生成算法英文叫CCG。理解这个算法的关键在于“主问题”和“子问题”的互动关系说通俗点就是切蛋糕和分蛋糕的博弈。子问题做的事情是给定第一阶段的机组启停和出力决策去寻找不确定性集合里最恶劣的那个风光出力场景以及在这个场景下最小的运行调整成本。这个“最恶劣场景”就是子问题要攻击你的武器。主问题做的事情是基于当前已知的最恶劣场景重新优化第一阶段的决策变量让成本尽可能低同时确保这些决策在已经发现的恶劣场景下都可行。主问题是在修补防线。CCG就是让这两者反复迭代——子问题不断找出更恶劣的场景主问题不断调整决策去应对。直到子问题找到的最恶劣场景不再让总成本变大就说明当前方案已经能抗住所有可能出现的情况这时候的解就是鲁棒最优解。这个思路理解透了后面的代码写起来才有方向感。我第一次写的时候就是没想清楚主问题和子问题各自到底在优化什么结果迭代逻辑写得一团乱。2. 动手搭建复现框架2.1 微电网系统结构与参数设定我复现时选了一个典型的交流微电网结构包含两台常规火电机组、一台储能系统、一组风力发电和一组光伏发电共同为一个24小时的日负荷曲线供电。系统参数采用了一个中等规模的测试算例两台火电机组装机容量分别是200MW和100MW储能容量80MWh最大充放电功率20MW风电装机150MW光伏装机50MW。负荷峰值出现在晚间大约240MW谷值在凌晨约120MW。火电机组的成本函数采用二次函数形式启停成本单独计算。储能的运行成本按充放电功率的线性函数折算同时加上一个较小的惩罚系数用来抑制储能的频繁切换行为——不加这个惩罚的话求解器可能会让储能每15分钟就切换一次充放电状态实际工程中根本没法这样操作。不确定性集合设在风电和光伏的预测出力上采用盒式不确定集合加预算约束的形式。预算参数Γ的作用是限制风光出力同时偏离预测值的程度Γ越大鲁棒性越强但经济性越差。我复现时把Γ从0到10逐一测试观察成本变化曲线这个曲线本身就是论文里最有说服力的一张图。2.2 编程语言与求解器选型建模语言我选了Python原因很简单生态好、上手快、出了问题容易查资料。优化建模框架用了Pyomo求解器方面主问题属于混合整数线性规划用Gurobi求解子问题线性化后也是线性规划同样交给Gurobi。这里有一个重要的经验如果你没有Gurobi的license用开源的CBC求解器也能跑通小规模算例但速度会慢不少而且数值稳定性稍差。我做参数敏感性分析的时候需要跑几十组实验最后还是换了Gurobi整体效率提升了不止一个量级。还有一个细节值得提醒Pyomo建模时变量的上下界、约束的写法、以及求解器的容差设置都会影响最终结果。我在复现过程中曾因为默认的MIP Gap设置过大导致主问题提前终止迭代结果跟论文差了将近5%。后来把MIP Gap调到了0.1%以下结果才对上。2.3 代码架构模块化设计让你少走弯路代码架构上我分成四个模块数据模块、建模模块、CCG主循环模块、结果分析模块。数据模块负责读入负荷、风光预测出力、机组参数、储能参数等所有原始数据统一打包成一个字典结构返回。建模模块包含三个函数构建主问题模型、构建子问题模型、构建初始场景的主问题模型每个函数内部完整定义了变量、目标函数和约束条件。CCG主循环模块是整个程序的核心调度逻辑它负责初始化不确定性场景、调用主问题求解、固定第一阶段的解后求解子问题、判断是否需要新增列与约束、更新迭代次数直到收敛。结果分析模块单独拎出来很有必要。复现过程中你需要反复对比不同参数下的结果如果结果分析代码和主程序耦合在一起每换一次参数就要重新跑一遍完整流程既浪费时间又容易出错。我把所有结果统一保存成DataFrame格式再单独写一个绘图脚本效率高很多。3. 两阶段模型的核心数学实现3.1 第一阶段的决策变量与约束第一阶段对应“日前调度阶段”也就是在不确定性实现之前就要做的决策。这里有两个层次的决策变量机组启停状态变量记作u_i_t是一个0-1变量以及机组出力的基准值记作p_i_t。为什么机组出力基准值也算第一阶段变量因为在两阶段鲁棒优化框架里第一阶段的决策是在知道风光实际出力之前就确定下来的如果之后风光出力偏离了预测值系统通过第二阶段进行调整。第一阶段的目标函数是机组发电成本加启停成本。约束包括功率平衡约束在第一阶段的预测场景下、机组出力上下限约束、机组爬坡约束、最小启停时间约束、储能的电量状态递推关系等。这里有一个建模上的坑。最小启停时间约束如果按完整形式写会引入大量额外约束主问题的规模会变得很大。实际上很多论文在求解时会对这个约束做松弛处理或者用更紧凑的等价形式。我在复现中发现对于24小时调度周期完整的启停时间约束写成线性不等式组是可行的但要注意不要写成非线性形式否则Gurobi会直接报错。3.2 第二阶段的对偶变换与线性化处理第二阶段的问题是在不确定性实现后通过调整机组出力、储能充放电功率来最小化运行调整成本。这个问题的约束包括功率平衡、机组出力上下限、储能约束等。不确定性参数u出现在约束的右侧也就是影响功率平衡方程。求解子问题最直接的方式是构造它的对偶问题因为内层的min问题对给定u求最小调整成本和外层的max问题找最恶劣u之间存在一个强对偶关系交换min和max后内层变成对偶问题的max外层是max的max合并成一个max问题目标函数变成线性函数加上双线性项。双线性项就是这个问题的核心难点——它由对偶变量乘以不确定性变量构成。处理双线性项有两种主流方法大M法和KKT条件法。大M法相对简单引入辅助变量和Big-M约束把双线性项线性化KKT条件法则更精确但它会把问题变成混合整数规划求解速度会慢一些。我在复现中先用了大M法因为实现简单、调试容易。但要注意Big-M的取值对求解数值稳定性影响很大——M取太小会错误地排除最优解M取太大又会导致数值病态。我的经验是M的取值要大于对偶变量的理论最大值的10倍以上同时对每个双线性项单独设置M值而不是统一用一个很大的数。3.3 不确定性集合的预算约束怎么写不确定性集合是两阶段鲁棒优化的灵魂。我复现时采用的是经典盒式集合加预算约束每个时段的预测误差在某个波动范围内但所有时段的波动总量不超过预算值Γ。具体来说风电的每个时段预测误差在正负0.2倍预测值之间取值预算Γ控制这个微电网24小时内最多允许多少个时段的误差同时达到边界值。Γ等于0的时候就是确定性模型Γ等于24的时候就是最保守的情况所有时段都取最恶劣值。这里直接关系到复现结果能不能和论文匹配。我在调试过程中发现不同论文对Γ的定义方式有差异有的论文Γ按“时段数”计有的论文按“时段乘以波动百分比”计复现前一定要看清楚原文的定义否则结果会差很多。4. CCG主循环的完整实现细节4.1 初始化场景与求解器的具体参数配置CCG算法启动前需要先初始化一个最恶劣场景。我选的是所有不确定性变量都取到盒式边界值的场景这样能让第一次迭代就暴露系统最极端的运行状态加快收敛。求解器参数方面我记录了Gurobi的关键参数设置MIPGap设为0.001TimeLimit设为600秒Threads设为4。这里特别注意Gurobi的MIPGap参数默认是1e-4但如果我们不显式设置某些版本下预求解阶段会做额外处理导致结果有微小偏差。我建议统一显式指定这些参数避免复现结果出现莫名其妙的误差。4.2 主问题如何新增变量与约束主问题的求解过程是这样的第一次迭代时主问题只包含初始化的那个恶劣场景。求解完后我们把主问题得到的机组启停状态和出力基准值固定下来带入子问题中求解出在已给第一阶段决策下最恶劣的场景和对应的调整成本。如果子问题求出的最恶劣场景对应的目标函数值与主问题当前目标函数值之差大于收敛阈值就把这个新场景对应的第二阶段变量和约束添加到主问题中重新求解主问题。新增的场景在代码中体现为一组额外的运行变量和一组额外的约束条件。你每添加一个场景主问题的变量数会增加约束矩阵的规模也会变大。这是CCG算法的固有特性——迭代次数越多主问题越大单次求解时间越长。4.3 收敛判据怎么设置才算合理收敛判据的设计直接影响算法效率和结果质量。我采用的是上下界差值的相对误差即上界减去下界除以下界绝对值阈值设为1e-3。这里有个细节需要注意上界是从主问题得到的但它的目标函数值计算的是一阶段成本加当前已列出的最恶劣场景下的二阶段成本下界则是把子问题求出的最恶劣场景代回主问题后主问题的最优值。这两个值在迭代过程中会逐步逼近最终收敛到同一个值。实践中我发现对于这个微电网算例通常在迭代10到15次之后就能收敛但如果初始场景选得不好或者MIP Gap设置过大迭代次数会明显增加。我后来加了一步“场景去重”的处理如果最新找到的场景与已有场景相同就提前终止迭代能省不少时间。5. 仿真结果与论文复现的关键对比5.1 鲁棒调度方案与传统确定性方案的成本对比复现完成后我对不同预算参数Γ值分别求解了调度方案记录了总成本、机组启停次数、储能充放电量等关键指标。当Γ等于0时模型退化为确定性调度总成本约52.8万元当Γ等于24时最保守的鲁棒方案总成本约61.2万元成本增加了16%左右。这个结果和原论文的趋势完全吻合。值得注意的是成本增加并非线性——Γ从0增加到8时成本增加很快但从8增加到24时成本增速明显放缓。这说明系统承受不确定性能力存在边际效应过大的Γ值带来的鲁棒性提升有限但经济代价显著增加。这个发现对实际微电网运行很有参考价值不需要追求最保守的Γ值选择一个适中的预算参数就能在鲁棒性和经济性之间取得较好的平衡。我复现时把Γ等于8的方案作为基准方案因为它既能覆盖大部分极端场景成本增加也控制在可接受范围内。5.2 微电网仿真中储能与机组的出力特征我观察了鲁棒方案下的出力曲线很有意思。确定性方案中储能系统主要在电价低谷时段充电、高峰时段放电辅助火电机组削峰填谷。但在鲁棒方案中储能的行为变得更保守——充电时机提前放电功率降低留出了更多的备用容量。火电机组的出力也有明显差异。确定性方案中成本较低的2号机组承担了大部分基荷1号机组只在高峰时段启动。而鲁棒方案中1号机组的运行时间更长甚至在部分中等负荷时段也保持运行。这其实就是鲁棒优化“牺牲经济性换安全性”的直接体现——更多机组在线意味着系统有更强的调节能力来应对风光出力波动。从储能SOC曲线上还能看到另外一个关键细节鲁棒方案下储能没有明显的满充满放循环SOC始终维持在20%到80%之间。这在工程上非常合理——储能满放虽然经济性更好但一旦遇到极端天气导致的风光出力骤降储能可能没有足够的剩余容量来支撑系统频率。5.3 用热力图和曲线图把结果讲清楚复现时我做了一张关键图表不同Γ值下各时段机组出力的热力图。这个图能直观展示随着鲁棒性增强火电机组的出力结构发生了怎样的变化。另外还画了储能SOC在不同Γ下的对比曲线以及CCG迭代过程中上下界的变化曲线。迭代收敛曲线是复现论文时最容易忽略但最有解释力的图。我建议你务必画一下——它能让审稿人或者导师一眼看出你的算法实现是否正确。正常收敛曲线应该呈现“上界快速下降、下界缓慢上升、最终趋于一致”的形态如果你的上下界一直在震荡或者收敛速度很慢大概率是迭代逻辑出了问题。6. 把方法迁移到矿山微电网场景6.1 矿山微电网的特殊约束与需求搜资料时看到“矿山微电网”这个词频繁出现很多做矿山能源管理的团队都在关注两阶段鲁棒优化。矿山微电网确实和普通园区微电网差异很大一是负荷波动剧烈大型破碎机、提升机启动瞬间会产生很大的功率冲击二是对供电可靠性要求极高断电会导致安全事故三是矿山一般位于偏远地区与大电网连接薄弱基本处于弱并网或孤岛运行状态。这些都让两阶段鲁棒优化变得格外适用——矿山微电网是最需要也最适合鲁棒优化的场景之一。不过复现时要在通用模型上做几处调整需要增加大功率冲击性负荷的建模这类负荷启动时会短时吸收大量功率发电侧如果包含瓦斯发电机组其出力受矿井瓦斯抽采量限制不是一个恒定上限储能系统的容量通常比常规微电网大因为矿山对离网运行时间长度的要求更高。6.2 矿用设备负荷建模把启停冲击写进约束矿山负荷建模是矿山微电网调度区别于常规微电网调度的关键。大型提升机或破碎机启动时启动电流可能是额定电流的数倍持续时间从几秒到几十秒不等。在小时级调度模型中这个冲击过程可以等效为一个时间段内的额外功率需求建模为启动事件触发的高功率时段。我在迁移时采用的方法是在功率平衡约束中增加一个冲击负荷的修正项冲击负荷用整数变量来表征是否启动同时设置冲击持续时段数。这样做的代价是增加了模型复杂度——每个冲击负荷都会引入额外的整数变量和约束但换来的是调度方案在矿下实际执行时不会因瞬时功率超限而跳闸。6.3 离网运行场景下鲁棒模型的调整矿山微电网的另一个特征是可能长时间离网运行。在离网模式下微电网没有任何外部支撑频率和电压全靠内部机组和储能维持。我在模型中用两个参数来刻画这种约束最小开机容量约束和旋转备用约束。最小开机容量约束要求在线机组的最大出力之和必须大于预测负荷加上一个安全裕度。旋转备用约束要求在线机组的可上调容量必须大于风光出力最恶劣波动加上负荷预测误差。这两个约束在并网模式下通常可以放宽一些但离网模式下必须严格满足否则最轻微的风光波动都可能造成频率越限。加上了这些矿山特有的约束以后我的模型从标准的24小时调度问题变成了一个和实际工程紧密结合的微电网调度工具。如果你正好在矿山能源管理部门工作或者在做矿山微电网的科研项目这部分内容可以直接用到你的方案设计中。7. 常见问题与调试心得7.1 子问题无界或不可行的排查思路复现两阶段鲁棒优化最常见的报错就是子问题在某种参数组合下无界或不可行。无界问题一般源于对偶问题构造错误。我在调试时发现如果功率平衡约束的行符号写反比如该用等式却写成了大于等于对偶变量会失去符号约束导致对偶问题无界。排查时最有效的方法是先检查原问题的约束方向是否正确再检查每个约束对应对偶变量的符号限制是否匹配。不可行问题则通常是因为第一阶段的决策使得某种极端场景下无法满足负荷需求。比如机组启停方案确定后在某些风光出力极低、负荷极高的场景下所有在线机组加储能的最大出力仍然不够。这时候要检查第一阶段约束是否遗漏了必要的备用容量约束或者不确定性集合范围是否过大。7.2 结果与论文对不上的三种主要原因复现论文时结果对不上我总结下来主要有三种原因参数不一致、模型细节简化、以及求解器容差设置不同。参数不一致是最常见的问题。很多论文正文只给出机组容量和成本系数爬坡速率、最小启停时间、储能效率等参数要么放在附录里要么干脆不写。这时候只能通过技术报告或者通信作者的主页去查原文的详细参数找不到的话就自己根据行业标准合理设置。模型细节简化也是一个重要因素。比如储能的充电和放电效率有的论文设为相同值有的设置为不同值再比如储能的自放电率有的考虑有的忽略。这些细微差别会让结果有几个百分点的偏差。求解器容差的影响也不能忽视。Gurobi的默认MIPGap是万分之一但有些论文在求解时可能设置了更大的容差导致他们得到的主问题最优值本身就是近似解。如果论文没有说明求解器设置只能自己多试几组参数看看结果对不上的主要原因到底在哪。7.3 提高求解效率的几个实用技巧复现过程中跑大量实验需要一个高效的代码主体。我总结了几条可行的加速策略都亲测有效。第一给主问题添加初始解。用确定性模型Γ等于0的最优解作为CCG主问题的初始解能让第一次迭代收敛得快很多大幅减少冷启动的搜索时间。第二子问题求解时固定第一阶段变量。CCG迭代中子问题只需要在第一阶段变量固定的情况下求解因此可以在子问题模型中预先固定这些变量减少搜索空间。Pyomo里可以用fix操作实现。第三对没有必要的约束做预消除。比如如果某些机组在部分时段因为最小启停时间的约束根本不可能改变状态就可以把这些时段的启停变量的整数性松弛掉变成连续变量也能明显降低求解难度。这些技巧单独看起来效果有限但叠加在一起能让整体计算时间从几十分钟缩短到几分钟对于需要跑几十组敏感性分析实验的场景来说价值非常大。8. 复现心得与下一步扩展方向整个过程跑下来我对两阶段鲁棒优化和CCG算法的理解深度提升了不止一个层次。第一次成功复现出和论文趋势一致的结果时那种成就感是单纯读论文完全体会不到的——你亲手验证了这套方法的每一个数学推导和代码实现。如果后面想继续深入我有几个天然的扩展方向把不确定性集合从盒式扩展到椭球式或者数据驱动式对比不同集合形状对调度经济性的影响考虑多微电网互联场景把两阶段鲁棒优化从单微网扩展到多微网协同调度也可以在模型中加入需求响应资源把可调负荷作为第三类调节手段。最后分享一个复现任何论文都适用的经验不要相信论文里任何一行公式和参数拿到原始数据后先自己推一遍模型再一行一行写代码然后不断对比中间结果。这个“慢”的过程恰恰是进步最快的过程。如果你正在复现类似的鲁棒优化论文希望这篇文章能帮你少踩几个坑。