源-荷双重不确定性下的虚拟电厂日前鲁棒优化调度MATLAB实现
发布时间:2026/10/19 4:27:20 锦皓数字建站

做虚拟电厂VPP日前调度最难的不是把方程组写出来而是你明明知道明天的风、光、负荷都有误差却还要在今天傍晚把所有机组的出力计划定下来。我之前做模拟项目X的时候就是用这套MATLAB代码反复跑“计及源-荷双重不确定性的虚拟电厂日前鲁棒优化调度”才把“不确定性”这三个字从论文里的摆设变成了真正能影响调度结果的约束。这篇内容主要拆解我在这套代码里做的三件事怎么把源、荷两边的预测误差建模成可求解的鲁棒不确定集怎么在目标函数、功率平衡、储能SOC这些核心约束里把不确定性“放进去”以及MATLAB YALMIP 商用求解器这条链路里哪些参数设置和坑位最容易让人怀疑人生。适合正在做虚拟电厂/微电网调度研究的人、需要把鲁棒优化落到算例上的同学也适合那些拿到一份代码但不知道从哪开始调参的工程师。1. 为什么日前调度非得把“源”和“荷”不确定性一起扛1.1 日前调度问题的本质提前一天做决策日前调度要求在日前通常是前一天下午确定未来24小时各台机组每个小时的出力计划、储能充放电计划、与上级电网的交换功率。决策做完之后第二天实际运行时只能在小范围内修正。也就是说你手里的“明天预测曲线”就是唯一的决策依据而预测曲线几乎不可能完全准确。这就形成了一个天然矛盾决策先行不确定性后到。如果你只用确定性预测去做计划第二天风和光稍一波动功率平衡就可能被打破。轻则弃风弃光重则切负荷。所以日前调度里鲁棒优化比随机优化更常用因为它不要求你猜概率分布只需要给出误差的上下界和最大偏离时段数就能生成一个“在最坏情况下依然可行”的方案。1.2 源、荷两边各自的“脾气”差异源侧和荷侧的不确定性表现差异很大。光伏出力误差主要集中在云层遮挡导致的阶梯式下跌预测偏差在午间最大风电出力误差则更随机风速的湍流特性会让出力在短时间内大幅波动误差分布相对更宽。负荷侧和它们又不一样居民负荷、商业负荷、工业负荷叠加之后存在明显的早晚高峰和季节偏移预测误差虽然相对温和但在极端天气或节假日会突然放大。不确定性来源时间特征典型预测误差量级对调度的影响方向光伏出力午间峰值时段误差大10%25%中午可能过量发电或突然少发风电出力全天波动明显15%40%任何时段都可能出现缺口或盈余负荷需求早、晚高峰误差明显3%8%高峰时段可能逼近供电上限我在代码里就是按照这三类误差的来源不同分别给它们设置了允许偏差范围。源侧给的偏差明显比负荷侧大因为实测下来风电和光伏的预测误差对调度结果的影响起主导作用。1.3 双重不确定性不是两个单边问题的叠加一个容易犯的认知错误是既然源侧有误差荷侧也有误差那把两个盒式不确定集分别写上不就行了实际问题没那么简单。最坏情况并不是“光伏出力最大”或者“负荷最小”这种单侧极值而是“光伏出力低于预测 负荷高于预测”的组合。反过来如果只考虑风电低出力的最坏场景可能没注意到负荷低谷时系统无法消纳过剩光伏。把两个不确定集简单耦合四个角点都可能被优化器取到最后得到的方案会非常保守成本高得没法看。所以我在这套代码里用的是“联合预算不确定集”允许每个时段的偏差在一定范围内变化但在时间维度上限制总偏差个数。举个直观例子不是所有24小时都同时取最坏值而是最多只有Γ个小时可以“闹脾气”。这样一来最坏场景不再是四个角点的暴力组合而是预算约束下的一组可实现的极端场景保守程度可由Γ控制这也是后面调参的核心抓手。2. 鲁棒优化建模目标函数、不确定性集合与功率平衡的实际写法2.1 目标函数怎么设计才符合虚拟电厂的真实成本结构虚拟电厂调度的目标函数不能只写“系统总成本最小”一句话。我在代码里拆成了这几块火电/燃气轮机的发电成本、储能充放电带来的损耗成本、与上级电网交互的购售电成本、以及弃风弃光和切负荷的惩罚成本。min Σ_t [ Σ_i (a_i·P_i(t)² b_i·P_i(t) c_i) C_grid(t)·P_grid(t) C_ess·(P_ch(t) P_dis(t)) λ_cur·P_curtail(t) λ_shed·P_shed(t) ]机组成本里的二次项要不要保留取决于求解器。用YALMIP搭配Gurobi或CPLEX可以直接解MIQP如果求解时间压不住就把二次项分段线性化转成MILP。惩罚项系数非常关键我这里给一组我自己常用的排序参考切负荷惩罚高于一切成本通常取系统最高购电价格的10倍以上购电成本按实际分时电价弃风弃光惩罚介于购电成本和机组边际成本之间储能损耗成本按电池循环寿命折算到每MWh。如果不按这个排序来优化器会做出非常离谱的事比如宁可弃风也不储能因为它算下来弃风更便宜。这个坑后面单独讲。2.2 三类常见不确定性集合的取舍鲁棒优化里不确定集的选择直接决定模型能不能求解、保守程度有多高。我在代码里对比过三种盒式不确定集、椭球不确定集、预算不确定集。不确定集类型数学形式特点保守程度求解器友好度适用场景盒式每个不确定参数独立落在区间内最保守高线性约束最坏情况明确、时段少椭球二次约束描述联合分布中等低需转二阶锥有统计协方差信息时预算Budget盒式 最多Γ个时段偏离可调节高需引入0-1变量大多数工程场景我最后选的是预算不确定集也就是Bertsimas-Sim那一套。它最大的好处是把不确定集的控制权交给了一个参数ΓΓ0时退化为确定性模型Γ24时等于盒式集合。你可以通过调节Γ在“鲁棒性”和“经济性”之间画出一条完整的帕累托曲线。对虚拟电厂这种以小时为粒度的调度问题这个特性非常实用。2.3 功率平衡约束的鲁棒化等式约束为什么难处理功率平衡约束是所有调度模型的核心写起来也很简单P_res(t) ΣP_gen(t) P_dis(t) - P_ch(t) P_grid(t) L(t)但一旦P_res和L都带不确定性并要求所有不确定场景下等式都严格成立问题就来了。唯一能满足“等号恒成立”的解往往是不存在的因为等式两侧的误差参数互相抵消不掉。很多第一次做鲁棒调度的人在这里被Infeasible逼疯。我的做法是把等式改写成两个不等式对应两个最危险方向上调场景新能源出力取低值负荷取高值要求系统可调电源和购电能力仍能满足平衡下调场景新能源出力取高值负荷取低值要求储能和电网交互能力能消纳多余电量。这两组不等式用同一组决策变量同时满足就相当于把所有可能的误差组合都兜住了。代码里我会额外加一个非常小的松弛变量ε允许极端情况下有轻微不平衡然后给ε一个高惩罚系数。这个技巧能大幅降低不可行的概率也不改变最优解的物理含义。2.4 储能SOC与机组爬坡在不确定场景下怎么约束储能SOC动态方程本身不含不确定量但它在鲁棒框架里的作用很大——储能是我模型里平衡偏差的主要执行者。定义如下E(t1) E(t) η_ch·P_ch(t) - P_dis(t)/η_dis一阶段鲁棒模型里P_ch和P_dis是固定的日前计划真正发生不确定场景时储能实际充放电会和计划不一样。所以我在代码里加了一个校验步骤用最优日前计划把“新能源低出 负荷高”和“新能源高出 负荷低”两组极端场景代入计算储能需要额外补偿的功率再检查SOC是否越限。如果校验发现SOC越限说明要么储能容量不够要么不确定集太激进。这时不是盲目调大储能容量而是先看Γ是否设得过大。机组爬坡约束同理爬坡约束只约束机组自身的调节速率但在极端场景下储能/电网交互承担的是快速平衡职能所以爬坡约束可以维持原样重点是把电网交互功率上限留出一定的备用裕量。3. MATLAB代码实现从YALMIP建模到求解器配置3.1 代码文件怎么拆数据、模型、求解、结果分开写这套MATLAB代码我按四个模块拆避免所有内容挤在一个脚本里改起来想死data_load.m读入预测曲线、误差上下界、分时电价init_params.m机组参数、储能参数、电网交互上限、惩罚系数、预算系数Γbuild_vpp_model.mYALMIP建模主程序输出Constraints和Objectivesolve_dispatch.m调用求解器输出各机组出力、储能SOC、成本明细plot_result.m把调度计划画出来。模块化的好处是改一组数据不用动模型改一个惩罚系数不用翻代码。我强烈建议把Γ也放在参数文件里因为算例对比时你最常动的就是它。3.2 不确定变量的声明与预算约束的0-1实现在YALMIP里确定性调度变量用sdpvar声明而预算不确定集的偏离状态用binvar声明。代码片段大概长这样% 时段数 T 24新能源电源个数为 N_res z_wind binvar(N_res, T); % 每个时段、每个电源是否偏离 Gamma 6; % 预算参数 Constraints [Constraints, sum(z_wind, 2) Gamma]; % 按电源分别约束 % 实际出力上下界预测值加减偏差由 z 控制偏离方向 P_wind_real P_wind_forecast Delta_wind .* (2*z_wind - 1);这段只是示意。实际建模时如果只关心功率平衡的两个最坏方向你可以把每个时间步的偏离方向固定也就是只保留“上调”或“下调”方向然后用sum(z) Gamma限制激活偏离的时段数。这样不仅模型规模小调试时也更清晰。3.3 对偶转换和Big-M取值的代码级注意点预算不确定集带来的max项不能直接丢给求解器得把最坏情况问题转换成线性约束。比如约束里出现max_{u∈U} a·u ≤ b我会利用强对偶把它替换成一组含对偶变量的线性不等式。代码层面要做的就是识别出所有含不确定量的约束逐个把max子问题写成对偶形式把对偶变量加进YALMIP的sdpvar列表原约束用对偶目标替代。这里最容易出错的是对偶方向。我先用Γ0跑一遍确定模型数值与确定性模型完全一致再用Γ1跑小规模测试手动构造极端场景验证结果通过了才上24小时大模型。大M法线性化时Big-M的取值也很敏感。我的经验是先跑一次去掉整数变量的松弛解观察相关变量的实际量级然后取该量级的23倍做M而不是随手写个100000。M太大MIP的松弛质量会变差求解器会在数值怪圈里打转M太小又会错误地把可行解剪掉。3.4 求解器选择与性能参数别让MIQP卡死YALMIP默认的求解器组合并不适合这种带0-1整数变量的鲁棒调度模型。我在这套代码里用的是YALMIP 商用求解器的组合实测CPLEX和Gurobi都能跑差别主要在二次目标项的处理上。ops sdpsettings(solver, gurobi, gurobi.mipgap, 0.0001, ... gurobi.timelimit, 600, gurobi.threads, 8);三个参数值得重点关注mipgap设为0.0001就够不需要追求绝对最优解调度计划差0.01%在工程上毫无区别timelimit600秒是个好阈值超过这个时间还没收敛应该回头查模型规模而不是干等threads多核并行对MIP有帮助但不是万能的模型本身太紧才是关键。如果MIQP跑不动优先把目标函数里的二次项做分段线性近似转成纯MILP。大部分情况是求解时间能下降一个数量级且结果几乎不变。4. 算例结果鲁棒参数对调度计划的影响4.1 测试系统设置与两种对照模型我用来做验证的是某园区虚拟电厂模拟系统结构很简单一台50MW燃气轮机、一套20MW/40MWh储能、光伏装机30MW、风电装机20MW与上级电网交互上限20MW。基础负荷峰值60MW。新能源和负荷预测曲线用实测数据叠加不同程度的白噪声生成。对照模型有3个确定性模型不考虑误差、鲁棒模型Γ2、鲁棒模型Γ8。其余参数完全一致。4.2 确定性调度 vs 鲁棒调度的成本对比指标确定性模型Γ2鲁棒Γ8鲁棒日前计划总成本21.6万元23.1万元25.8万元最坏场景切负荷量6.2MWh0.4MWh0MWh最坏场景弃风量8.5MWh2.1MWh0.6MWh储能SOC波动范围25%90%15%95%5%99%求解时间8秒42秒126秒有意思的是确定性模型在最坏场景下的表现非常差切负荷直接到6MWh以上。这正好说明了为什么“计划成本低”不等于“真实运行成本低”确定性计划把系统卡得太紧一遇到风和光不给面子就得付出很高的惩罚代价。鲁棒模型在Γ2的时候计划成本只高了7%但最坏场景的切负荷量从6.2MWh降到了0.4MWh这个性价比是很划算的。Γ继续加到8虽然彻底消除了切负荷但成本上涨幅度明显变大储能也更早接近满充状态。4.3 Budget该怎么选从历史预测误差分布找拐点我见过不少人拿Γ当玄学要么随便取个中间值要么干脆取满。实际上Γ的取值应该和你的预测误差分布直接挂钩。我的做法是把历史每个时段的预测误差归一化统计“连续多少个时段同时出现大偏差”的累计概率。比如某组历史数据里连续3个时段同时出现较大偏差的场景占比大约92%连续5个时段的占比接近99%那Γ取46通常就够覆盖绝大多数极端情况再多取就是纯花钱买安心。实际操作时我会先跑Γ从0到12的完整成本曲线找到“成本上升速率明显变陡”的拐点。拐点之前的Γ值用很小的成本代价换来了很可观的鲁棒性提升拐点之后成本上升快但鲁棒性改善有限这时候就该停下来。成本曲线拐点才是工程上选Γ的最靠谱依据。4.4 储能角色的变化最容易验证模型是否有问题的地方鲁棒模型里储能的行为和确定性模型有非常明显的区别。确定性模型里储能主要做“低充高放”的套利SOC曲线跟着电价走很有规律。但鲁棒模型里储能的前半段经常被压着SOC不会轻易跑到高位因为它还要留出容量应对可能出现的出力偏差到了后半段为了满足下调场景的消纳需求储能又会集中充电。如果你跑完自己的代码发现鲁棒模型的储能SOC曲线和确定性模型几乎一模一样那大概率有问题——要么不确定集没真正生效要么储能在你的约束里没有参与不平衡调整的通道。这是一个很好的自检信号。5. 调试中容易踩的五个坑以及我的排查思路5.1 对偶方向反了鲁棒求解结果反而比确定性更便宜正常来说加了鲁棒约束之后成本只可能上升或持平不会下降。如果你的鲁棒模型运行出来的总成本比确定性模型还低几乎可以断定是对偶转换时方向搞反了导致优化器找到了一个“假最坏场景”实际是一个被放松的约束。排查方法很简单先设Γ0看鲁棒模型输出是否和确定性模型完全一致不一致一定建模有问题。然后再挑一个单时段的极端场景把不确定参数固定成最坏值代入最优解验证约束是否真的满足。5.2 等式约束含不确定变量模型直接不可行这是我最常见的Infeasible来源。一看到功率平衡等号左右两边都有预测误差变量直接套用原来的等式约束结果就是无解。解决办法前面已经说了把等式拆成两个最坏方向的不等式并加入可调的小松弛惩罚量。调试时有一个小技巧先把不确定集全部“关掉”Γ0、偏差设0确认模型可行然后在不确定集里只放开一个电源、一个时段逐步排查是哪个约束和哪个不确定项冲突。5.3 惩罚系数倒挂优化结果全在“弃风”如果目标函数里弃风惩罚只有20元/MWh而电网购电价格是80元/MWh那优化器当然会倾向于把所有多余风电都弃掉反正更“便宜”。这不是你模型错了是成本结构错乱。我一般的处理方式是切负荷惩罚最高取10000元/MWh量级弃风弃光惩罚介于售电价格和购电价格之间比如500元/MWh储能损耗按实际循环寿命折算。惩罚系数之间必须保持合理的相对关系模型做出来的调度计划才符合物理直觉。5.4 Big-M乱设求解器陷入数值灾难用大M法线性化双线性项和逻辑约束时M值设成100000结果就是求解器整页警告数值问题耗时翻倍。我的调试经验是先从LP松弛结果里观察相关变量的范围通常取这个范围的23倍就够了。比如系统最大功率50MWM取150不要取50000。M的大小还会影响对偶间隙M太大时MIP的LP松弛解离整数解非常远分支定界要遍历大量节点。跑不动的时候优先检查所有M值。5.5 SOC闭合约束与鲁棒场景冲突成本虚高很多算例会习惯性地加SOC_T SOC_0这在确定性模型里很正常。但鲁棒模型里储能需要在一整天内为不确定场景留调节空间硬性闭合等于强制电池在一天结束前把多充的电全部放掉很可能和最优鲁棒策略冲突导致成本虚高甚至不可行。我的建议是去掉硬闭合条件或者放宽为SOC_T ≥ 0.9·SOC_0。如果你论文里需要展示“日周期调度”的概念也请务必解释清楚这个约束对鲁棒问题带来的额外限制。5.6 求解时间不可控先检查MIP gap和二次项鲁棒模型一旦加上整数变量就变成MILP/MIQP求解时间比LP高一个数量级。此时先别急着优化模型结构先看MIP gap。如果gap长期在5%以上说明整数解质量很差最可能是Big-M取值问题其次是目标函数二次项让求解器陷入大量节点的剪枝困境。把二次项分段线性化之后大多数情况下gap会明显降下来。再不行就缩小时间窗先用6小时的小规模模型验证算法流程正确性再跑24小时的完整模型。跑完整模型之前把timelimit设上别让求解器无限跑下去。最后再分享一点实操感受这套MATLAB代码前前后后跑了大概一个多月我最深的体会是鲁棒优化调度这件事建模能力占一半调试能力占另一半。第一次把不确定集加进去的时候YALMIP报错信息看得人头皮发麻后来每次改模型都先跑Γ0对照再逐步加不确定项整个链路就变得可控多了。如果你拿到这套代码准备改成自己的场景建议第一步不要急着换数据。先用我的参数从头到尾跑一遍把Γ从0慢慢往上调看成本变化曲线和储能SOC曲线的走势确认模型行为符合物理直觉然后再替换你的机组参数、电价曲线和预测数据。否则一旦换了数据出了问题你会分不清是数据问题还是模型问题。后面要扩展的话最值得做的是把一阶段模型升级成两阶段鲁棒用列与约束生成CCG去迭代求解。“这里投多少、实时怎么调”之间的差别才是虚拟电厂调度的完整形态。但前提是先把一阶段这套逻辑吃透CCG的很多坑本质上都源于对一阶段鲁棒建模的理解还不够深。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。