1. 研究背景为什么偏偏要在日前和日内之间做“分时治理”先聊一个比较根本的问题虚拟电厂Virtual Power Plant, VPP做调度优化为什么不干脆把所有决策放在一个时间尺度里同时求解非得拆成日前调度和日内调度两个层次先说结论因为真实电力系统的决策节奏本身就不是同步的。市场出清、机组启停这类决策通常是提前一天甚至更早定的而实时功率分配、爬坡修正这类决策只需要提前几分钟到几小时判断。如果把不同时效性的决策揉进同一个优化模型一方面数学模型会变得异常庞大动辄几万个变量加几万条约束求解时间根本无法接受另一方面风电、光伏出力的预测精度随时间跨度急剧下降——提前24小时的预测误差可能超过20%提前1小时的误差却能压到5%以内。让误差大的预测数据去指导前置决策本身就是一种资源错配。我最初复现这类论文时也尝试过只做一个“单层大模型”把所有分布式电源、储能、可控负荷的24小时决策一次性求解。结果就是模型确实能算出来但算出的出力曲线在第二天实际执行时几乎完全走样尤其是光伏午间的出力尖峰和傍晚负荷晚高峰之间的功率缺额预测偏差一放大整个调度计划就得推倒重来。后来才明白学术界和工程界已经非常默契地把这个问题拆成两个层次第一层叫日前调度Day-Ahead Scheduling时间粒度为1小时决策窗口是未来24小时。它解决的是“明天整体怎么运行”的问题哪些机组要启停、储能大致是充还是放、可控负荷的削峰计划怎么安排。这一层不追求每个时刻的精确功率但必须保证全局经济性最优所以本质是一个大规模混合整数规划或混合整数二次规划问题。第二层叫日内调度Intraday Scheduling时间粒度可以是15分钟甚至5分钟决策窗口通常是未来1到4小时的滚动区间。它解决的是“未来几十分钟到几小时怎么修正”的问题光伏云层遮住了一部分出力储能要不要临时多放一点某个负荷突然降到预测值以下柴油机组要不要降载。这一层的数据更加新鲜预测精度高所以能对日前计划中的偏差做针对性修正。这个“先全局规划、再局部修正”的逻辑有点像做一次长途旅行出发前一晚你先规划好大致的路线、酒店和加油点这是日前调度但真正开车上路后你要根据实时路况、天气随时改走哪条匝道、在哪个服务区休息这就是日内调度。两层决策互不替代缺一不可。很多复现失败的案例问题恰恰出在把这两层混为一谈或者干脆只做了一层就声称是“多时间尺度”。1.1 虚拟电厂里到底“藏”着哪些资源在具体建模之前得先把虚拟电厂的资源画像说清楚。不同论文对虚拟电厂的资源假设差异很大但一套典型的配置通常包括以下四类风力发电与光伏发电这两类是可再生能源主体在虚拟电厂中通常处于“优先消纳”地位。但它们也有一个共性——出力具有不确定性且不可控。建模时我们一般给它们设定预测功率曲线并允许在日内调度阶段有一定的上下波动。储能系统BESS这是虚拟电厂里的“调节阀”。储能能在电价低谷时段充电在高峰时段放电既能套利又能平衡可再生能源波动。建模时最需要关注的是荷电状态SOC的递推关系即某时刻的电量等于上一时刻电量加上充电功率减去放电功率还应考虑充放电效率的差异。柴油发电机或燃气轮机这是虚拟电厂里少有的可控出力单元。它们响应速度快、出力可调但燃料成本高通常只在可再生能源出力不足或负荷尖峰时段启用。建模时要考虑上下限、爬坡速率、启停成本。可控负荷比如温控负荷、工业生产线中的可中断负荷。这类资源在虚拟电厂里承担“需求侧响应”的角色在电价高峰时段可以削减部分用电量以换取补偿收益。以上四类资源每类都需要在日前和日内两个尺度下分别建模。区别在于日前调度对风速、光照、负荷的预测采用的是24小时前的预测数据日内调度则采用滚动更新的超短期预测数据并加入对不确定性的惩罚项。1.2 两个时间尺度不是简单的“加细”这里要特别提醒一个容易误读的点日内调度不是简单地把日前调度的步长从1小时改成15分钟重新算一遍。两者的角色有本质区别。日前调度的核心是经济性全局最优。它要考虑启停、电价、燃料成本、购售电策略为第二天全天的运行划定一个“基线计划”。这个基线计划会提交给电网调度机构是虚拟电厂对外承诺的发电/用电曲线。日内调度的核心是跟踪误差修正。它是在日前计划已经确定的基础上根据最新预测偏差以最小调整成本为目标重新分配储能、柴油机组和可控负荷的功率让虚拟电厂的实际出力尽可能逼近日前申报值同时保证系统安全。换句话说日前调度决定的是“目标曲线”日内调度决定的是“怎么纠偏”。从这个角度来看两层的优化变量虽然很像但约束条件和目标函数侧重点完全不同。我见过不少初学者把日内调度模型直接做成日前调度模型的小版本甚至连SOC递推、爬坡约束都照抄结果算出来的“日内调度”只是把日前曲线加密了完全失去了修正的作用。这是复现中最典型的认知偏差。2. 日前调度建模粗粒度下的全局优化日前调度是48小时不一般是24小时时间间隔1小时一共24个时段。更学术地说这是一个混合整数规划问题核心是找到一组机组启停状态量和各时段功率分配量使得全天总运行成本最小。2.1 目标函数该算的账一分别漏日前调度目标函数通常由四部分构成向电网购电/售电成本收入虚拟电厂与外部电网存在功率交换。当虚拟电厂内部发电不足时需要从电网购电当内部发电过剩时可以售电给电网。购电电价高于售电电价所以目标函数中这一项要区分正负。柴油机组燃料成本通常用二次函数拟合即燃料成本与出力呈二次关系。机组启停成本每次启动或停机都要付出额外代价尤其是柴油机组冷启动成本较高。储能退化成本储能频繁充放电会缩短寿命有些模型中会用充放电功率的线性项来近似表征。于是目标函数可以写成[ \min \sum_{t1}^{24} \left( C_{buy}P_{buy,t} - C_{sell}P_{sell,t} \sum_{g} (a_g P_{g,t}^2 b_g P_{g,t} c_g) \sum_{g} SU_{g,t} C_{bess}(P_{ch,t}P_{dis,t}) \right) ]这里 (P_{buy,t}) 和 (P_{sell,t}) 是虚拟电厂与电网的交互功率(P_{g,t}) 是第g台柴油机组出力(SU_{g,t}) 是启停成本(P_{ch,t}) 和 (P_{dis,t}) 是储能充放电功率。如果后续在Matlab中用YALMIP工具箱建模这个目标函数可以直接用sum()写成一个标量表达式。2.2 约束清单七大类约束一个都不能少建模的难点在约束而不是目标函数。日前调度模型中必须包含以下七类约束功率平衡约束每个时段虚拟电厂内部总发电功率加上购电功率必须等于总负荷加售电功率。这个约束是我在复现中踩过坑的地方——不少论文在功率平衡中忽略了储能自身运行时可能产生的损耗或者把充放电各自独立建模后平衡方程写错符号导致优化结果出现能量不守恒的情况。储能约束包括荷电状态上限下限、充放电功率上下限、充放电状态互斥约束、以及SOC递推方程。其中SOC递推方程是最容易被写错的充电效率和放电效率不同不能用一个统一效率替代否则一天的SOC轨迹会出现明显漂移。柴油机组约束包括出力上下限、爬坡速率约束、最小启停时间约束。爬坡约束在时间尺度较粗时影响不明显但到了日内调度15分钟粒度下这个约束几乎决定了柴油机能不能跟上光伏波动建模时千万不能丢。可控负荷约束包括最大削减时长、削减次数限制、削减调节速率等。部分简化模型中可控负荷只作为一个可平移的功率块参与调度此时只需要约束平移的时段范围。购售电约束与电网交互的功率不能超过联络线传输容量且同一时段不能同时购电和售电。后者可以通过引入0-1变量来约束但会增加建模复杂度部分论文用大M法或直接省略得到的结果在边界处可能不合理。日前申报曲线约束虚拟电厂作为一个整体向电网提交次日的出力计划该计划需要符合机组实际能力和电网安全要求一般体现为联络线功率上下限约束。可再生能源出力约束风机、风电的日前预测出力作为已知参数输入实际调度功率不得超过预测值且不能低于0。一个较为完整的日前调度模型变量规模大约在几百个到一千多个。用YALMIP商业求解器求解通常几秒到几十秒就能算出最优解。但需要注意混合整数变量的引入会让问题变成NP-hard如果求解器配置不当很容易出现长时间的“冷启动”后才开始进入分支定界。2.3 日前预测数据该怎么准备做日前调度最头疼的不是写模型而是整理输入数据。数据通常包括24小时负荷预测曲线或者净负荷曲线。24小时风电、光伏预测出力曲线。分时电价曲线峰、平、谷三段。各柴油机组的成本系数、启停成本、出力限值、爬坡率。储能系统容量、初始SOC、最大充放电功率、充放电效率。在实际复现过程中我通常直接用经典的测试系统数据比如某开源案例库提供的微电网数据或者直接从某篇论文附录里提取数据。如果没有现成数据可以自己生成一条带噪声的典型日负荷曲线再叠加风电、光伏的预测曲线。生成时要注意风电曲线和光伏曲线在时间上应该具有互补性——风电半夜强、白天弱光伏白天强、夜间为零这样才能调动储能的跨时段调节能力让调度结果显得更有分析价值。3. 日内调度建模细粒度下的滚动修正如果说日前调度是“慢决策、粗步长、全局看”日内调度就是“快决策、细步长、局部调”。这一层的建模是我认为复现中最能体现论文水平的地方。3.1 日内调度的目标让“偏差”付出的代价最小日内调度发生在实际运行当天此时距离真实运行时刻只有几小时甚至几十分钟。超短期预测精度显著提高但日前计划又是不可轻易推翻的所以日内调度的本质是在尽量不偏离日前计划的前提下以最小运行调整成本修正功率偏差。数学表达上常见的做法是在日前调度的目标函数基础上加入一个“计划偏离惩罚项”。每一项调整量进入目标函数时都带有权重系数这样模型会优先选择调整成本最低的资源来消化偏差。比如储能调整成本较低柴油机组调整成本较高可控负荷削减补偿费用居中那么优化结果通常会先动用储能的小时级平衡能力再启用可控负荷最后才会调整柴油机组出力。3.2 日内调度的建模周期与滚动窗口日内调度最常见的模式是“滚动优化”即每到一个新时刻重新优化未来一段时间的调度计划。比如每15分钟触发一次优化优化窗口为未来4小时也就是16个时段。这里有三个重要参数滚动触发间隔每15分钟还是每30分钟触发一次优化。优化窗口长度通常取未来1~4小时不宜取满24小时否则信息优势无法体现。控制时域只执行该窗口内第一个时段的调度指令然后等下个触发时刻重新优化。滚动窗口的设置在Matlab代码中通常用一个循环来实现循环内调用优化模型并更新输入数据。值得一提的是窗口长度越长求解时间越长但对未来变化的适应性越好。两者之间需要权衡复现时可以通过敏感性分析来展示不同窗口长度对成本的影响。3.3 日内调度新增的约束要“盯着”日前计划走与日前调度不同日内调度模型里会多出一组“计划跟踪约束”。为了衡量实际功率与日前计划之间的偏差定义[ \Delta P_t P_{vpp,t}^{日内} - P_{vpp,t}^{日前} ]然后对该偏差施加软约束即将偏差的平方或绝对值加入目标函数而不是硬性限制。这种“软约束惩罚”的处理方式在数学上比硬约束更稳健因为硬约束在极端场景下会导致无解而软约束永远有解。此外日内调度中可再生能源的实际出力不再是固定预测值而是一个区间。通常用预测区间上下限的方式来刻画不确定性。在模型中我可以把光伏、风电的出力作为可调变量但限制在置信区间内让模型自行安排最经济的出力点。这种做法比单纯用期望值更科学也更能体现日内调度的“不确定性处理能力”。3.4 日内调度的求解难度日内调度的模型规模通常比日前调度小因为它只需要覆盖未来几个小时的时段。但它的求解频次很高一天可能要触发几十次优化所以对求解速度的要求反而更高。如果每次优化都要花几分钟那滚动调度根本没有实际意义。在Matlab实现中我用YALMIP工具箱进行建模然后调用求解器求解。对日内调度这种中等规模混合整数规划求解时间通常能控制在几秒内。如果发现求解时间过长优先检查是不是因为引入了过多的0-1变量可以考虑把机组启停状态固定为日前结果日内只优化功率分配这样问题就退化成线性规划或二次规划求解速度会提升一个数量级。4. Matlab代码实现从数学模型到可运行代码的关键细节论文里的公式是一套数学语言而Matlab代码是另一套工程语言。从公式到代码中间有大量的细节需要落实。这一节我讲几个复现时最常遇到的实现痛点。4.1 求解器与建模工具箱的选择在Matlab环境中搭建优化模型目前的主流方案是使用YALMIP工具箱配合外部求解器。YALMIP的优势在于它提供了一套统一的建模语法你不需要关心求解器内部的数据结构只需要把目标函数和约束用符号表达式写出来然后调用solvesdp()或optimize()函数即可。代码框架大致如下% 定义决策变量 P_buy sdpvar(24, 1); % 购电功率 P_sell sdpvar(24, 1); % 售电功率 P_dg sdpvar(24, 3); % 三台柴油机组功率 SOC sdpvar(25, 1); % 储能荷电状态多一个边界点 P_ch sdpvar(24, 1); % 储能充电功率 P_dis sdpvar(24, 1); % 储能放电功率 % 定义约束 constraints []; % 功率平衡约束 for t 1:24 constraints [constraints, P_buy(t) sum(P_dg(t,:)) P_dis(t)*eff_dis P_w(t) P_pv(t) load(t) P_sell(t) P_ch(t)/eff_ch]; end % SOC递推 for t 1:24 constraints [constraints, SOC(t1) SOC(t) (P_ch(t)*eff_ch - P_dis(t)/eff_dis) / cap]; end % 调用求解器 options sdpsettings(solver,gurobi,verbose,2); result optimize(constraints, objective, options); P_buy_value value(P_buy);这里特别说一下value()函数的用法。在YALMIP中求解完成后所有决策变量的具体数值都要通过value()提取否则你拿到的只是一个符号变量。我在实践中的习惯是求解完成后写一个collect_result()函数把所有关键状态量打包成一个结构体方便后续统一画图和统计分析。4.2 矩阵化建模还是循环建模对于初学者来说最直观的是用for循环逐时段写约束。这种写法可读性好但代码冗长而且速度偏慢。对于24时段的日前调度循环写法完全没问题但对于动辄几十次滚动优化的日内调度循环内的重复建模会拖慢整体运行速度。更高效的方式是采用矩阵化建模。把所有决策变量堆叠成矩阵约束通过矩阵运算一次性生成。好消息是YALMIP本身是支持矩阵约束的比如SOC递推可以直接写成constraints [constraints, SOC(2:end) SOC(1:end-1) (P_ch*eff_ch - P_dis/eff_dis)/cap];这种写法不仅简洁而且能显著减少建模时间。我的建议是先把循环写法的版本跑通再逐步改成矩阵写法保证两种方式的结果完全一致。这样既能降低调试难度又能获得足够快的运行速度。4.3 日前调度与日内调度的变量衔接两时间尺度模型的衔接是复现的核心。日前调度结束后我们需要把日前计划中的关键变量作为日内调度的“基准参数”传递过去。具体包括各时段机组的启停状态固定为0-1值不再作为日内决策变量。各时段储能SOC的日前计划值作为日内SOC跟踪的参考点。各时段虚拟电厂对外联络线功率的日前计划值作为日内偏差惩罚的参考点。柴油机组的日前计划出力作为日内出力调整的基值。在Matlab代码中这些衔接通过简单的变量赋值即可完成但要注意日前求解完成后必须立即用value()提取数值否则后续日内调度会用到符号变量导致求解器报错。还有一点容易被忽略日前调度的时间粒度是1小时而日内调度的粒度如果是15分钟则存在数据的时标转换问题。我需要把日前调度结果的1小时间隔值插值到15分钟间隔或者把日内调度所需的预测数据全部聚合到1小时后再比较。这里推荐使用interp1函数进行线性插值并在探究“日前计划跟踪”目标时注意插值带来的偏差必要时对计划值做阶梯保持而非线性插值。4.4 数据文件组织方式代码的组织方式直接影响到复现效率。我的习惯是建立以下目录结构project/ ├── data/ # 存放输入数据 │ ├── load_data.mat │ ├── wind_pv_data.mat │ └── price_data.mat ├── main_DA.m # 日前调度主程序 ├── main_ID.m # 日内调度主程序 ├── main_run_all.m # 总运行脚本顺序执行日前-日内-结果分析 ├── model/ # 存放模型构建函数 │ ├── build_DA_model.m │ └── build_ID_model.m └── results/ # 存放输出结果总运行脚本的理念是“一键复现”。任何审稿人或工程师拿到这个文件夹直接运行main_run_all.m就能复现全部结果。这一点对复现类工作非常重要。5. 复现路上的坑从数值病态到约束丢失复现SCI论文里的代码永远不会一帆风顺。以下是我在复现多时间尺度虚拟电厂调度过程中真实踩过的几个坑都很有代表性。5.1 爬坡约束在时间尺度转换中被“稀释”了日前调度的时间粒度是1小时爬坡约束是1小时尺度上的速率限制日内调度的时间粒度是15分钟如果直接把爬坡约束按比例缩小看起来没问题但忽略了柴油机组的实际物理响应特性。柴油机组在短时间内的爬坡能力并不与时间线性等比它有一个初始的“快速响应段”和后续的“持续爬坡段”简化建模时至少应保证日内调度的爬坡约束比日前调度更严格。我在第一次复现时因为没有单独调整爬坡约束导致日内调度给出的柴油机组调整量在15分钟内根本无法实现可算出的成本却显示“优化成功”。这种错误在做结果验证时才会暴露——功率曲线的跳变值超过了机组物理极限但目标函数值却看起来非常漂亮。后来我在日内模型中加入了更严格的爬坡约束结果才变得合理可用。5.2 SOC初始值设置不当导致无解储能SOC递推方程中初始SOC通常设为固定值如50%末端SOC通常设为可变量或有约束区间。但有些论文会要求末端SOC回到初始值附近以保证储能调度具有日周期可持续性。问题来了如果初始SOC设定过高充电空间不足而夜间风电大发时又必须充电就会导致约束互相冲突、模型无解。解决办法有两种一是把末端SOC约束从硬约束改成软约束在目标函数中加入SOC偏差惩罚项二是在建模前检查一天内的电量平衡关系确保充放电总量与初始、末端SOC相匹配。这个检查逻辑可以写成一段预处理代码在正式优化前先快速判断数据是否自洽。5.3 日前调度结果“过于激进”导致日内无法跟踪有时候日前调度为了追求经济最优会在某一个时段把储能放得很低、柴油机组关得干净但到了日内实际运行时光伏突然降低或者负荷上升这时候已经没有足够的储能电量或机组启动时间来不及导致日内调度无论如何修正都无法弥补偏差。这类问题的本质是日前调度没有为不确定性预留调节裕度。高级的论文会引入鲁棒优化或随机规划来内生处理这种风险但常规复现只需要在日前模型中增加一个储能末端SOC的“安全带约束”比如要求日内每个时段结束时SOC不低于20%就能很大程度上缓解这个问题。这种小技巧在论文里往往只是若干约束中的一行但在实际运行中意义重大。5.4 数值病态与求解器参数设置虚拟电厂调度模型中各成本项的数量级差异可能非常大。比如购电成本可能是几千上万而储能退化成本可能只有零点几如果数值差异超过1e6求解器内部的数值稳定性就会受到挑战甚至出现对偶不收敛或错误的最优性判断。我的经验是建模前先对所有输入参数做一个量纲审查确保所有功率的单位一致kW还是MW、所有成本单位一致元还是万元、所有时间单位一致小时还是分钟。如果发现某些项的系数特别小可以考虑对其进行归一化预处理或者适当放大惩罚项权重。另外为求解器设置合理的MIP gap容差比如0.1%也可以有效缩短求解时间不必追求完全精确到绝对值。6. 一个完整算例与结果解读理论讲再多不如跑一个完整算例直观。下面我给出一个经典配置的测试结果分析思路方便各位对照自己的复现代码。6.1 案例配置与数据设定假设一个虚拟电厂包含2台风力发电机总装机1.2MW、1套光伏系统装机0.8MW、1台柴油机组容量0.5MW、1套储能容量0.6MWh最大充放电功率0.3MW以及一段可控负荷最大可调0.2MW。负荷曲线取某工业园区的典型日负荷分时电价采用峰谷三段制风电光伏出力曲线取同一地区典型季节的预测数据。我将这个案例跑了一遍完整的日前调度日内滚动调度。日前调度步长1小时日内调度步长15分钟滚动窗口4小时一天共触发96次日内优化。笔记本电脑上总运行时间约3分钟其中日内调度占据了大部分时间。6.2 结果对比一储能SOC曲线的差异日前调度的SOC曲线与日内调度的SOC曲线存在明显差异。日前调度只在整点时刻给出SOC值曲线较为平滑大致呈现“夜间充电—上午小放—午后光伏大发时急充—晚高峰深度放电”的形态。日内调度由于每隔15分钟刷新一次预测和优化SOC曲线出现了更多的小幅调整但整体趋势与日前计划保持一致。这个对比说明日内调度确实在“跟踪日前计划”而不是另起炉灶。如果你的复现结果中日内SOC与日前SOC差异很大甚至有反向趋势大概率是漏掉了计划跟踪约束或者惩罚权重设置不合理。6.3 结果对比二柴油机组出力与运行成本日前调度中柴油机组在夜间负荷低谷时段几乎不出力但在早晚高峰时段会有明确的出力计划。日内调度中由于光伏实际出力略低于预测值柴油机组在午后到傍晚时段被追加了额外出力。这部分新增出力对应的燃料成本就是“不确定性代价”。把全天运行成本拆开来看日前调度总成本相对较低因为它是基于理想预测做的全局规划日内调度总成本更高一些多出来的部分可以理解为为了应对不确定性而支付的“调节成本”。在多时间尺度调度研究中对比这两个成本的差值是一种非常直观地衡量日前预测准确性的手段。6.4 复现这类论文的几点心得最后说几条个人经验权当参考。第一不要迷信论文里的每一个数字。论文为了表达简洁可能隐去了一些边界条件、系数处理或约束细节。复现时你应当把论文当作“算法框架”而不是“规格说明书”。很多参数需要根据数据情况自行标定。第二一定要做“约束存在性”验证。方法是把目标函数改成0只求可行解。如果即使在目标函数为0的情况下依然无解那说明约束本身存在矛盾需要回头检查模型如果可行解存在但目标值异常再检查目标函数的量纲和符号。这个“先可行、后最优”的两步法几乎能排查掉90%的建模错误。第三重视结果可视化。调度曲线、储能SOC、柴油机组出力、电网交互功率这些关键结果一定要画成时间序列曲线来检查。曲线比表格更能暴露问题——充放电切换的异常频繁、SOC的突变、功率缺额的高峰一眼就能看出来。画图用Matlab自带的plot函数就够了叠加日前和日内的对比曲线时记得用不同的线型和颜色否则发布到论文或博客里读者很难读懂。虚拟电厂多时间尺度调度看起来是“日前加日内”两个模型组合实际上考验的是对电力系统运行逻辑的深层理解。模型写得好不好取决于对约束的把握是否精准代码跑得快不快取决于矩阵化能力和数据组织能力。如果你照着这个思路把前后端全部打通再回头去读相关SCI论文会发现那些公式和图表都变得亲切许多。