资讯详情

资讯详情

多阶段鲁棒优化在微电网调度中的MATLAB实现:基于CCG算法的不确定性建模实战

做电力系统优化调度的朋友十有八九会遇到这种情况论文里写着“考虑可再生能源不确定性”仿真模型却还是拿预测曲线当作真实值把风电、光伏直接固定成一个确定出力调度方案看起来漂亮一到实际运行偏差一大就崩。我前阵子完整复现了一个含可再生能源和储能的区域微电网最优运行模型属于多阶段鲁棒调度框架就是把“不确定性”真正放进了约束里求解出来的是在最坏场景下依然可行的调度策略而不是只对一条预测曲线有效。这篇内容没有任何藏着掖着的部分从模型假设、数学建模、不确定性集合设计到MATLAB里YALMIPGurobi的具体实现再到CCG列与约束生成迭代求解的全过程都会讲清楚。适合正在做微电网调度、鲁棒优化入门、或者想把某个鲁棒调度模型从论文变成可运行程序的读者。我会把复现过程中的建模细节、代码逻辑、调试经验和踩坑记录都写出来尽量让你看完之后能自己搭出一套能跑、能出图、能对比的完整程序。1. 为什么微电网调度必须谈“鲁棒性”不确定性建模的动机1.1 风电光伏和负荷的“变脸”让确定性调度吃过多少亏先说个很常见的现象。很多刚接触微电网调度的同学第一版程序往往是这样写的把光伏、风电的历史数据平均一下或者说用某天的实测曲线当预测值然后扔给求解器一个混合整数线性规划出来一组机组出力和储能充放电计划。结果论文复现出来“效果很好”成本曲线也非常漂亮但拿到真实数据一验证母线电压越限、储能电量不够用、柴发爬坡跟不上各种问题全来了。问题出在哪出在把随机量当作确定量处理。可再生能源出力的随机波动幅度往往很大晴天和阴天、有风和无风之间光伏或风电的出力可能相差50%以上。负荷侧也没有省油的灯居民负荷、充电桩负荷在一天内的峰谷差极大。如果你只在期望值附近做优化那你的调度方案只是“在平均情况下最优”而不是“在任何可能出现的场景下都安全”。鲁棒优化换了一个思路我不赌预测准不准而是假设不确定性会在一个限定范围内任意取值我求出来的调度方案必须保证即使在最恶劣的那组取值下系统依然能正常运行、不切负荷、不越限。这就是“鲁棒”二字的含义——宁可牺牲一点经济性也要换取可靠性和安全性。1.2 “先计划、后调整”的两阶段决策逻辑这里要引出多阶段/两阶段鲁棒的思想。微电网调度不是一次性把所有变量都定死而是分两层第一阶段做预决策也就是在知道不确定性实现之前先决定机组的启停、储能的充放电预计划、备用容量等需要提前安排的量第二阶段是调整决策也就是当风电光伏和负荷的真实值观测到之后在预决策的基础上对可控机组出力、储能实际充放电功率、弃风弃光量、切负荷量进行调整。这个逻辑其实特别像我们平时做规划先订机票酒店第一阶段决策必须提前定到了目的地之后根据天气和现场情况决定要不要加一件外套第二阶段决策可以随时调整。第一阶段决策要“保证任何天气都能应付”所以模型里第二阶段必须对最坏情况都有可行解。论文标题里写“多阶段鲁棒调度模型”落到实际程序里往往是两阶段鲁棒优化的实现但叫“多阶段”是因为决策变量在时间维度上是滚动推进到24小时甚至96小时粒度的第一阶段变量和第二阶段变量在每时段的对应关系都要建模。理解了这个逻辑你再看市面上那些带“鲁棒”字样的调度模型基本都能瞬间看懂它到底在做什么。2. 复现前先定盘子拓扑参数与不确定性集合的设计2.1 以某个园区微电网为例拓扑与负荷参数复现之前先把“舞台”搭好。为了把模型讲具体我的复现框架是这样的一个含光伏、风电、储能、柴油发电机和外部电网联络线的区域微电网按典型日24小时调度步长1小时。光伏额定容量500 kW预测曲线按典型晴天设置风电额定容量300 kW预测曲线带明显的夜间峰值储能系统额定容量600 kWh最大充放电功率150 kW效率95%SOC范围20%~90%柴油发电机2台单台额定功率200 kW有最小技术出力30 kW启动成本与运行成本分别计联络线与上级电网交换功率上限200 kW购售电价格分时负荷方面我采用典型工业园区的日负荷曲线峰值约600 kW谷值约250 kW。这里有一个很关键的准备动作所有数据都需要归一化或者换算成同一基准值很多同学程序跑不通不是模型错了而是单位混了——kW和kWh、元/MWh和元/kWh混在一起结果完全没法看。建议所有功率用kW能量用kWh电价统一换算成元/kWh成本结果最后再汇总成元。2.2 不确定性集合的选择盒式、椭球式与1-范数式的取舍鲁棒优化最核心的设计自由度就是不确定性集合怎么取。它直接决定了模型的保守程度和求解难度。我在复现中对比了三种常见方案。第一种是盒式集合也就是直接给每个时段的风电、光伏、负荷设一个波动区间比如预测值上下浮动15%。这是最保守的因为不确定性向量里的每一个分量都同时取极端值导致调度结果特别“贵”实际运行中几乎不会出现所有时段所有电源同时偏离最大极限的情况。第二种是椭球式集合它用二阶范数约束描述不确定量的联合分布范围数学上对应二阶锥约束求解器处理起来会引入二阶锥规划代码量和求解时间都会上升。第三种是预算约束集合——也就是1-范数/带预算的盒式集合这是目前最主流的选择。它在盒式的基础上加入一个“不确定性预算Γ”来限制所有时段偏差绝对值的总量本质是把“所有时段同时出极端值”这个过于保守的情形排除掉。我做复现时用的就是带预算约束的集合[ \sum_{t1}^{T} \frac{| \Delta P_{w,t} |}{\hat{P}_{w,t}} \le \Gamma_w ]其中(\hat{P}{w,t})是预测值(\Delta P{w,t})是偏差量。Γ越大代表越保守Γ0退化成确定性模型Γ24时等价于盒式集合因为总有24个时段。实际复现时我把风电、光伏、负荷分别设了不同的预算值目的就是调节模型对三类不确定性的保守程度。2.3 储能、柴发、联络线的运行约束参数定好之后约束条件要写完整这一步直接决定你的模型是不是可解的。储能约束的关键是SOC递推和同一时段不能同时充放[ SOC_t SOC_{t-1} \eta_{ch} P_{ch,t} - \frac{P_{dis,t}}{\eta_{dis}} ] [ P_{ch,t} \le P_{ch}^{max} \cdot u_{ch,t}, \quad P_{dis,t} \le P_{dis}^{max} \cdot u_{dis,t} ] [ u_{ch,t} u_{dis,t} \le 1 ]柴发约束主要有最小启停时间约束、爬坡约束和出力上下限。最小启停时间需要引入启停状态变量和启动变量用一组线性约束表达比如[ \sum_{it}^{tUT-1} u_i \ge UT \cdot y_t ]其中(y_t)是t时段是否启动的0-1变量UT是最小运行时间。这块在YALMIP里用二进制变量处理起来不算难但如果你用线性规划求解器跑纯连续模型就体会不到这个细节了。联络线约束主要限制购售电功率上限以及购电和售电不能同时发生。一般可以用一个0-1变量区分购售电状态也可以简单设置购电价低于售电价让求解器自动“不会同时买卖”但在鲁棒模型里因为存在max-min结构建议还是显式加购售状态变量避免子问题里出现“倒卖电力”的伪解。3. 多阶段鲁棒调度的数学框架目标函数与约束体系3.1 目标函数开机成本运行成本失负荷惩罚整个模型的目标函数被我拆成了三块。第一块是确定性相关的成本包括柴发的燃料成本和启动成本、储能充放电的折旧维护成本、从上级电网购电的费用。这里的柴发燃料成本通常用二次函数表示但为了保持MILP可解性我做了分段线性化处理用三段的线性函数逼近。启动成本则直接对应启动变量的系数。第二块是鲁棒优化里的核心——最坏场景下的调整成本。包括在最恶劣不确定性实现下的柴发额外出力成本、储能调整成本、弃风弃光惩罚和失负荷惩罚。目标函数的形式是min Max也就是先对所有可能的不确定性场景取最大成本再对决策变量求最小。[ \min_{x} \left( c^T x \max_{u \in U} \min_{y} d^T y \right) ]失负荷惩罚价格要设置得非常高比如5000元/MWh这样才能保证模型在追求经济性的同时不会出现为了省钱而故意切用户负荷的荒唐方案。弃风弃光惩罚适中就行比如300元/MWh表达了“优先消纳新能源”的政策导向。第三块是第一阶段成本与第二阶段成本的线性组合。实际上在CCG框架里第一阶段和第二阶段是交替求解的最终收敛后得到的目标函数值就是上层min的最优值。3.2 第一阶段约束机组启停、储能预调度、备用设定第一阶段约束里最容易被忽略的是备用约束。鲁棒模型里备用容量不是单独加一个“留5%备用”的简单公式而是通过不确定性集合和第二阶段可行性约束隐式地体现出来的。我复现的时候在模型里加了一条总备用约束要求每个时段柴发最大可调出力加上储能可用容量与联络线可增购部分之和不小于风电、光伏、负荷的不确定性总波动幅度。这样能确保无论不确定量怎么变系统都有调节手段。第一阶段变量的定义是柴发启停0-1变量、储能充放电状态0-1变量、储能充放电功率、柴发基点出力、联络线功率。这里“基点出力”的概念很关键它是在不确定性实现之前的计划值后续第二阶段会在基点附近做调整调整量受爬坡和上下限约束。3.3 第二阶段约束最坏场景下的功率再平衡第二阶段是在不确定性值确定后通过调整变量实现功率平衡。调整变量包括柴发出力修正量、储能充放电修正量、弃风弃光量、失负荷量、联络线功率修正量。这些变量的可行域和第一阶段约束类似但多了功率平衡等式约束[ P_{g,t} \Delta P_{g,t} P_{pv,t} P_{w,t} P_{dis,t} \Delta P_{dis,t} P_{buy,t} \Delta P_{buy,t} P_{load,t} P_{ch,t} \Delta P_{ch,t} P_{sell,t} ]左右两边分别对应发电侧和负荷侧。注意储能充电功率和放电功率是分开定义的不要合并成“正为放电负为充电”的单变量形式否则0-1状态约束会很难写。这个平衡约束要在每个时段、每个最坏不确定性组合下都成立这正是子问题要处理的难点。4. MATLAB实现核心CCG算法与YALMIP建模4.1 为什么选CCG而不是Benders求解两阶段鲁棒优化教材里常见两类算法Benders分解和CCGColumn-and-Constraint Generation列与约束生成也就是我这次用的CCG。Benders分解是给主问题不断加“最优性割”和“可行性割”主问题变量规模增长较慢但对含有0-1变量的模型来说割的紧致性往往不好迭代次数会特别多。CCG的思路反过来它把子问题识别出来的最坏场景作为新的约束和变量一次性塞回主问题每个最坏场景对应一组新的第二阶段变量。这样主问题规模虽然增长更快但边界收敛极快一般迭代三五次就能达到很高精度。对微电网调度这种时段多、变量维度高的问题CCG的收敛速度优势非常明显。我实测下来CCG通常4~6轮就能收敛到10^{-3}精度而用Benders可能要迭代几十次才能接近这个水平。4.2 主问题怎么构、子问题怎么拆我把CCG框架拆成两块来说。主问题MP的结构是变量第一阶段变量 已经识别出来的若干个最坏场景对应的第二阶段变量目标第一阶段成本 sum(每个最坏场景下的第二阶段成本)约束第一阶段约束 每个已识别场景下的第二阶段约束主问题每次求解后第一阶段变量x会更新。x代表“针对所有已知最坏场景都可行的调度方案”。随着迭代轮数增加最坏场景越来越多x会越来越保守直到覆盖所有真正可能的最坏情况。子问题SP是用来“找茬”的给定主问题求出的x去寻找一个不确定性值u让系统的第二阶段成本最大。子问题结构是[ \max_{u \in U} \min_{y} d^T y ] [ s.t. \quad A y \ge b - B x - C u ]这是一个max-min双层问题不能直接求解。需要转化成单层max问题。4.3 子问题双层max-min的单层化强对偶与KKT子问题的转化是整篇程序里最容易写崩的地方也是“完全复现”这四个字含金量最高的环节。对于内层min问题如果它是线性规划我利用强对偶定理把它转成max问题。内层min的对偶问题[ \max_{\lambda} \quad \lambda^T (b - B x - C u) ] [ s.t. \quad A^T \lambda \le d, \quad \lambda \ge 0 ]这样min-max就变成了max-Max也就是一个单纯的最大化问题目标中含有双线性项(\lambda^T C u)。由于(\lambda)和(u)都是连续变量双线性项仍然难以直接求解但对等式型约束可以继续用KKT互补条件来消解或者引入大M法和二进制变量处理乘积项。我的做法是用KKT条件处理功率平衡等式把内层min问题完全替换成它的KKT系统再进行线性化。这一步如果手动推导工作量很大我建议你先写出内层问题的标准对偶形式用YALMIP的双线性求解器验证一遍结果然后再把对偶形式手动写成约束放进子问题。4.4 迭代流程与收敛判据整个CCG程序的骨架长这样初始化给定一组初始不确定性场景比如所有偏差为零即预测场景求解主问题得到第一阶段决策x和当前目标值下界LB将x带入子问题求解得到最坏场景u*以及对应第二阶段成本如果第二阶段成本加上第一阶段成本小于上界UB则更新UB如果UB-LB小于容差停止否则把u*对应的约束和变量加入主问题回到第2步收敛判据我取了0.01元这种尺度也就是目标值变化小于1分钱就停止实际跑起来一般4轮左右就达到。注意这里UB和LB的gap计算方式要跟目标函数的成本量级匹配如果成本是几万元级别gap取10^{-2}绝对够用再小只是白白增加迭代轮数。YALMIP核心代码结构可以用下面这个框架来理解% 主问题变量定义 x sdpvar(..., full); % 第一阶段变量 y cell(nScenarios, 1); % 每个场景的第二阶段变量 for k 1:nScenarios y{k} sdpvar(..., full); end % 主问题约束 Constraints [ ... 第一阶段约束 ... ]; Objective c * x; for k 1:nScenarios Constraints [Constraints, ... 场景k的第二阶段约束 ...]; Objective Objective d * y{k}; end % 求解 optimize(Constraints, Objective, sdpsettings(solver, gurobi, verbose, 0));子问题部分需要单独建模每次迭代时利用value(x)把第一阶段变量固定下来再对u和λ联合求解。5. 我复现时踩过的坑从报错到结果验真5.1 变量维度与sdpvar定义踩坑YALMIP里最隐形的坑就是变量维度。我刚开始写的时候把第二阶段变量定义成sdpvar(1, T)也就是一行24列而约束矩阵是24行结果YALMIP报了一堆维度不匹配的警告然后自动把变量broadcast了最后得出来的结果全是错的。这种错往往不会导致程序崩溃只会在结果里悄悄埋雷。我的经验是所有与时段相关的变量统一用sdpvar(T, 1)的列向量形式配合repmat构造系数矩阵同时约束表达式里保持维度对齐。每次写完约束先用size(Constraints)检查约束数量再去求解。如果发现约束数量比预期少了八成就是哪一行变量维度塌了被YALMIP静默处理了。还有个坑是YALMIP里用optimize求解时如果你没有显式指定integrality某些版本会把binary变量当成连续变量处理导致求出来的“最优解”根本不是整数解。我在程序开头加了yalmip(clear)并且设置sdpsettings(solver,gurobi)后检查int变量的个数确保二进制变量确实传给了求解器。5.2 储能SOC跨时段约束的细节储能SOC约束看起来就是一行递推公式但复现时我吃过大亏。第一版程序把SOC初值设成了50%结果优化出来的策略是“开局先放光后期再充满”看起来成本很低实际上是因为SOC初值给了一个“免费的初始能量”这种方案在真实运行中根本不可行。正确做法是给SOC一个范围约束并把初值也纳入优化变量或者设定SOC初值等于终值日循环约束保证一天运行前后储能电量一致。这个约束在鲁棒模型里尤其是对子问题有影响因为最坏场景下储能可能被压到下限如果SOC终值不归位第二天就没法继续调度了。我加的是SOC_0 SOC_T且SOC_0固定为50%的约束块。另一个细节是充放电效率的处理。效率不是作用在SOC递推的功率上而是作用在能量上。具体来说放电时实际从电池里取出的能量要除效率充电时要乘效率这个如果写反了用着用着电池能量就越算越多出现“永动机”。5.3 收敛判据太松/太严的表现收敛判据这个参数我一开始随手设了1e-6结果程序跑到第八轮还不收敛原因是成本量级在5万元左右绝对gap到1e-6根本不可能稳定达到最后子问题来回震荡。后来我把gap设成0.01元四轮就收敛了。观察CCG的迭代曲线你会发现一个很有意思的现象UB是先快速下降再缓慢下降LB是先快速上升再缓慢上升两条线像剪刀一样慢慢合拢。如果某轮LB突然变大且子问题目标值出现异常大数多半是子问题对偶变换出了错比如对偶变量符号反了、约束方向写反了。这个事我排查了很久最后是拿一个单时段的简版模型手算了一遍才定位到是某个约束的右端项符号写错了。6. 结果分析与灵敏度验证如何让人相信你的调度是“最优”的6.1 与确定性调度和各场景对比复现完成后第一步就是验证结果是否合理。我把鲁棒调度结果与确定性调度做了对比你会发现鲁棒方案的总成本通常高5%~8%。高出来的这部分就是“鲁棒溢价”——为了应对不确定性而预留的调节能力和备用容量成本。注意鲁棒方案的特点是柴发开机台数更多、储能留出的可调空间更大、联络线的传输功率更保守。这些结果一定要能讲出道理来如果鲁棒方案和确定性方案完全一样或者鲁棒成本反而低那大概率是模型约束写错了。我建立一个验证方法把求出的鲁棒方案放回到蒙特卡洛随机场景里测试。生成500组符合不确定性集合分布的场景逐一检查功率平衡是否满足、SOC是否越限、柴发爬坡是否够用。鲁棒方案在500组场景里全部可行而确定性方案有大约20%的场景会出现切负荷或者功率越限。这样的对比放在论文里就是非常有说服力的结果。6.2 不确定预算与置信水平的灵敏度灵敏度分析是审稿人和导师最爱问的部分也是你验证模型逻辑自洽性的试金石。我做了一组数据把风电、光伏、负荷的不确定预算Γ从0逐步增加到12、24对应的系统总成本会单调上升。这个单调性是鲁棒优化的理论性质——不确定性集合越大可行域越紧缩最优成本只可能变高或者持平。如果你跑出来成本下降了那肯定有bug。Γ值对储能策略的影响也很有看头Γ较小时储能主要做峰谷套利也就是低价充电、高价放电Γ增大后储能策略会被迫转为“随时准备应对功率缺口”削峰填谷的功能反而弱化这也是鲁棒模型区别于确定性模型的一个显著特点。实际做项目时可以根据运营方对风险的容忍度来选Γ怕出事的选大值追求经济性的选小值。6.3 扩展方向与后续工作这套框架的扩展空间非常充裕我列几个自己试过或者见过可行方案的方向加入需求响应把可平移负荷、可削减负荷纳入第二阶段调整变量让负荷侧也成为备用资源多微电网互联把单个微电网扩展成多个微网互联的集群不确定性集合里就要加入微网间功率交换变量鲁棒性要求会进一步提高引入条件风险价值约束在鲁棒模型里加入CVaR约束把尾部风险控制在一定水平以下兼顾鲁棒性和经济性全年8760小时滚动调度把典型日模型改成全年滚动框架决策时段从24小时扩展到8760小时不确定性预算按季度分段设置这些方向任何一个做深了都足以形成一篇有分量的期刊论文或者一个实际落地项目。我自己的体会是鲁棒调度模型的难度不在算法本身而在建模的颗粒度和对实际运行规则的理解。同一个模型约束里少了一条爬坡约束求出来的“最优解”真到了现场就会原形毕露。最后再分享一点复现心得不要在代码里硬塞过多的“高级算法”。我见过有人非要在两阶段鲁棒外再套一层粒子群、遗传算法来“优化不确定性预算”结果调试成本翻了十倍效果却不比遍历几个Γ值更好。先用CCG把基础模型跑通再一步步加复杂度这是最稳妥的路。拿到收敛的调度结果后记得回过去画几条典型日的功率平衡曲线亲眼看一看储能充放电和柴发启停是否符合常识和物理直觉。程序是会骗人的但物理规律不会。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →