1. 项目概述为什么虚拟电厂需要“两套调度系统”这两年“虚拟电厂”在电力圈的热度一直没降过。简单说虚拟电厂不是物理上的一个电厂它把光伏、风电、储能、充电桩、可控负荷这些分散的资源聚合起来通过统一调度让这些资源像一个电厂一样对外输出电力或提供调节能力。但“聚合”这两个字说起来容易真正做起来最难的是调度——什么时候充电、什么时候放电、什么时候切负荷、什么时候从电网买电这些都是要提前想清楚的。这就引出了标题里的核心问题多时间尺度调度。我见过不少初学者拿到一个虚拟电厂的项目第一反应就是“我做一个24小时的优化模型不就行了”。但实际跑下来你会发现今天预测明天光伏能发1000度结果明天一块云飘过来实际只有600度这时候你按原计划运行要么储能不够放要么要临时高价买电全部乱套。这就是为什么目前主流的研究方向都做两个时间尺度日前调度和日内调度。这个项目标题点得很明白对应的是某篇高水平SCI论文的复现工作代码用Matlab实现。如果我是准备做这个方向的人我最关心三件事第一这个模型怎么建目标函数和约束条件是什么第二两个时间尺度是怎么衔接的第三代码怎么组织求解器怎么选跑出来的结果怎么解读。这篇文章就按这个思路把整个项目从头到尾拆开来讲既适合刚接触虚拟电厂的学生也适合已经做了一段时间、想看看别人怎么设计模型的老手。2. 两层优化模型的整体设计思路我最早接触虚拟电厂调度的时候犯过一个错误把日前和日内两个模型混在一起写想用一个大的优化模型搞定所有事。后来发现这完全走不通原因不只是求解复杂度的问题更关键的是两个时间尺度要回答的问题根本不一样。2.1 日前调度定“计划”日内调度修“偏差”日前调度的时间分辨率通常是1小时有的论文用24个点有的用96个点15分钟一个点。它的作用是在电网发布次日电价、天气预报给出次日光伏和风电预测曲线之后算出一个完整的运行计划——储能在哪个时段充电、哪个时段放电可调负荷怎么安排跟电网的交易量是多少。这个计划追求的是整体经济性最优相当于“排班表”。日内调度的逻辑完全不同。它的时间分辨率更细可能是15分钟甚至5分钟一个点滚动窗口一般取未来1到4小时。用的是更新的预测数据比如超短期光伏预测、实时负荷监测在日前计划的基础上只做修正。这就像你排好了明天的班表但早上发现一个人请假了你不是重新排一整张表而是就地调整几个岗位。日内调度只负责处理预测误差追求的是“跟得上变化”。这两种角色的差异决定了模型结构日前模型要含0-1整数变量设备启停、储能充放电状态是一个MILP问题日内模型因为状态已经被日前锁定更多是连续性调整核心是功率分配和爬坡约束。2.2 目标函数怎么定才合理很多复现项目第一个卡住的地方就是目标函数。虚拟电厂作为一个市场主体最朴素的逻辑是最大化收益也就是售电收入加上参与辅助服务市场的收入减去购电成本和设备运行维护成本。用数学语言写出来大概是$$ \max ; \sum_{t} (\lambda_{s,t}P_{s,t} - \lambda_{b,t}P_{b,t}) - C_{op} $$其中λ是电价P是功率下标s表示售电、b表示购电。但实际写代码的时候98%的情况下你会把这个最大化问题转换成最小化问题因为求解器对最小化问题更友好而且方便加入惩罚项。转换方法很简单前面加个负号就行。这里我要特别提醒一个细节储能设备的运行维护成本一定不能漏。我看过太多复现代码目标函数只算了电价收益结果优化出来的结果是储能每个时段都在充放——因为对模型来说频繁充放没有任何代价它当然往死里用。加了运维成本项储能才会合理控制充放次数这才是符合实际的。2.3 约束条件是模型的“地基”约束条件决定了模型的可行域这块出了问题整个模型就跑飞了。最基础的五类约束必须写全功率平衡约束虚拟电厂内部所有资源出力之和加上与电网的交互功率等于负荷需求。这是硬约束必须满足。储能约束包括SOC状态转移方程、充放电功率上下限、SOC上下限。注意有些论文不允许同一时段既充电又放电这一条在很多复现代码里是用一对0-1整数变量实现的是求解变慢的主要来源。光伏和风电出力约束实际出力不能超过预测值这是上限约束简单但容易漏。可调负荷约束负荷转移量有上限而且一个调度周期内转移的总量要守恒——你今天把某个时段负荷挪走了后面还得挪回来。与电网交互约束购售电功率上下限以及购售不能同时进行。这些约束在日内模型里大部分会保留但形式会简化。因为日内调度不考虑储能启停状态的变化SOC的0-1变量被拿掉问题规模小很多求解速度快。3. Matlab代码实现的方案选型与代码架构项目标题里特意标注“Matlab代码实现”那我先说说我为什么觉得这个选择很合理。做电力系统优化的人对Matlab有天然的依赖不是因为它快而是因为它方便。3.1 求解器方案Yalmip加Cplex/GurobiMatlab本身不提供混合整数规划的求解能力所以必须借助外部工具箱。目前学术圈和工程圈最主流的搭配是Yalmip作为建模语言Cplex或Gurobi作为底层求解器。Yalmip的生态在电力系统领域极其成熟几乎每一篇电力系统优化的论文附带的代码都用它。它的核心价值在于让你用“数学语言”直接写模型不需要关心求解器内部的表达格式。需要提醒一点Gurobi对MILP的求解速度通常比Cplex快尤其是大规模问题。如果条件允许我个人建议用Gurobi学术许可申请也方便。两个求解器的Yalmip调用方式完全一样换求解器只需要改一行代码。3.2 代码文件结构怎么组织拿到这个项目如果只给一个孤零零的脚本文件那复现起来会很痛苦。我自己在复现SCI论文代码的时候一定会拆成几个模块。建议的文件结构是这样VPP_Scheduling/ ├── main_day_ahead.m % 日前调度主程序 ├── main_intraday.m % 日内调度主程序 ├── data/ │ ├── load_profile.mat % 负荷曲线 │ ├── pv_predict.mat % 光伏日前预测数据 │ ├── pv_realtime.mat % 光伏超短期实测数据 │ ├── price_day_ahead.mat% 日前电价 │ └── price_intraday.mat % 实时电价 ├── models/ │ ├── build_day_ahead.m % 日前优化模型 │ ├── build_intraday.m % 日内优化模型 │ └── constraints/ │ ├── add_storage_con.m % 储能约束 │ ├── add_balance_con.m % 功率平衡约束 │ └── add_grid_con.m % 电网交互约束 └── utils/ ├── plot_results.m % 结果可视化 └── calculate_metrics.m % 经济性指标计算主程序负责读数据、调用建模函数、求解、展示结果。模型文件只负责构建目标函数和约束约束文件按类型拆分。这样做的好处是想换一组数据、想改一个约束条件只需要动对应的小文件不用在几百行的脚本里search半天。3.3 日前调度模型的代码骨架下面这段代码是日前调度主循环的核心逻辑我用伪代码和真实代码混合展示方便你对照自己的项目理解。%% 日前调度主程序 % 定义时间参数 T 24; % 日前调度24个时段1小时一个点 % 定义变量 X sdpvar(4, T); % X(1,:) 光伏出力, X(2,:) 储能放电, X(3,:) 储能充电, X(4,:) 电网交互 SOC sdpvar(1, T1); % 储能电量状态, SOC(1)为初始电量 u_ch binvar(1, T); % 充电状态0-1变量 u_dis binvar(1, T); % 放电状态0-1变量 % 目标函数最大化收益转为最小化负收益 objective -sum(price_day.*X(4,:)*dt) sum(op_cost.*(X(2,:)X(3,:))*dt); % 约束条件 Constraints []; % 功率平衡约束 Constraints [Constraints, X(1,:) X(2,:) - X(3,:) X(4,:) load_forecast(1:T)]; % 储能SOC递推约束 Constraints [Constraints, SOC(2:T1) SOC(1:T) (etac*u_ch.*P_rated - X(3,:)/etad)*dt]; % ... 其他约束省略 % 求解 ops sdpsettings(solver, gurobi, verbose, 2); optimize(Constraints, objective, ops);注意代码里u_ch和u_dis是二元变量这是为了保证储能不会同时充电又放电。实际约束里还要加一行u_ch u_dis 1否则这两个变量还是可能同时为1。这一行特别重要漏了的话结果会显示储能一边充电一边放电功率平衡倒是满足了但物理上完全说不通。3.4 日内滚动调度的衔接方式日内调度和日前调度在代码上的衔接核心在于“初值传递”。日内调度不是从零开始重新优化而是把日前调度已经确定的某些状态作为边界条件。具体来说滚动调度从当前时刻开始往后开一个4小时窗口。当前时刻储能的实际SOC是已知的从日前计划算出来或者实际测量。在这个窗口内模型只优化各资源的出力调整量相对于日前计划的偏差要加一个惩罚系数——因为调度中心希望在修正偏差的同时不要跟原计划偏离太远否则连锁影响其他资源。日内循环的核心代码结构for k 1:K % K个滚动窗口 % 读取超短期预测数据 window_data pv_realtime(k:kwindow_len-1); % 获取当前时刻的SOC初值 soc_init SOC_result(k); % 构建日内模型注意时间窗变短了且不含0-1变量 [model, objective] build_intraday(window_data, soc_init, ...); % 求解 optimize(model, objective, ops); % 只取第一个时段的决策结果执行窗口后移 action value(X(:,1)); record(k, :) action; end这里有个重要的实操要点滚动优化只执行第一个时段的决策然后窗口后移重新求解。原因很简单预测是会更新的你现在基于预测算出来的后续计划等真到了那个时候可能已经过时了所以宁可每15分钟重算一次也不要贪图省事把整个窗口都执行完。这也是“滚动”两个字的精髓。有些初学者第一次写完代码直接开整段结果最后发现跟踪效果很差问题往往就出在这里——没有理解“只执行第一步”这条规则。4. 数据构建与关键参数设定复现SCI论文的代码最怕的不是模型难而是复现出来的结果跟论文对不上。原因八成出在数据上。论文里的数据是经过精心处理的你需要还原它的生成逻辑。4.1 基础数据从哪里来虚拟电厂调度的数据分三类价格数据日前电价使用分时电价日内使用实时电价。分时电价好找很多公开数据集都有实时电价的波动特性可以通过在日前电价上叠加一个随机扰动来模拟。预测数据光伏和风电的预测曲线可以从实际历史数据上加一个预测误差得到。误差建模一般用正态分布标准差设为预测值的10%到20%。负荷数据虚拟电厂内部聚合的负荷曲线可以用典型日负荷曲线缩放得到。我在复现这个项目的时候数据是模拟生成的方法如下先拿到一条典型工业负荷曲线作为基础然后在上面叠加了随机波动光伏出力用Beta分布生成风电用Weibull分布生成这个选择是有依据的——光伏出力受光照强度影响Beta分布能比较好地描述光照强度的随机性风速则更常被建模为Weibull分布。4.2 储能参数怎么设置储能的参数设置是复现时特别容易跟论文结果产生偏差的地方。几个关键参数储能额定容量MWh、最大充放电功率MW、充放电效率、初始SOC、SOC上下限。一个比较标准的设置是容量2MWh功率0.5MW充电效率0.95放电效率0.95初始SOC 0.3SOC范围0.1到0.9运维成本0.01元/kWh。注意SOC上下限不要设成0和1实际电池不允许完全放空或充满而且优化求解时边界约束越紧模型越不容易“钻空子”。4.3 置信水平与不确定性处理这是和一般调度模型最大的区别点。如果只是简单的确定性优化预测给多少就用多少那和本文要求的“多时间尺度”就没什么关系了。真正的高水平论文会在日内模型中引入不确定性处理最常见的两种方式是场景法对光伏出力的预测值生成多个随机场景比如100个优化目标变成这些场景下收益的期望值。优点是考虑全面缺点是模型规模变大。鲁棒优化设置一个不确定集合优化目标是在最坏情况下尽可能不亏钱。优点是方案保守可靠缺点是过于保守——真实运行中你大概率不会碰到最坏情况。这个项目用的是哪个方法取决于原论文。但复现的时候我建议两种都试试因为你能通过对比两种方案的收益直观体会到“不确定性的代价”。这也是论文里常放的对比图之一。注意如果你的电脑配置一般场景法会明显拖慢求解速度。我实测100个场景的时候模型变量数量直接膨胀了几十倍求解时间从几十秒拉到几十分钟。这时候要先从10个场景跑通再逐步增加。5. 核心环节的实现细节与约束处理目标函数谁都会写约束条件才是真正体现功力、也最容易踩坑的地方。在这个项目里有几类约束的处理方式值得拿出来单独讲。5.1 储能SOC的连续性约束SOC约束是储能模型的核心。它用数学语言描述电池电量的变化规律这一时刻的电量等于上一时刻的电量加上充电量乘以充电效率减去放电量除以放电效率。$$ SOC(t1) SOC(t) \eta_{ch} P_{ch}(t) \Delta t - \frac{P_{dis}(t)}{\eta_{dis}} \Delta t $$这里有一个极其常见的问题初始SOC怎么定以及最后SOC要不要约束回初始值。如果只是简单设定一个初始值优化出来的结果往往是——最后时刻把SOC放干因为模型觉得把电全放掉最赚钱。但实际运行中电池要在第二天继续用不可能放干。所以正规做法是加一个末端SOC约束比如SOC(T1) SOC(1)表示一个调度周期结束电量至少回到初始水平。这个约束不加你的优化结果在最后一个时段一定会出现“异常惊喜”别问我怎么知道的。5.2 电网交互功率约束虚拟电厂和外部电网的交互通常被抽象成一个“联络线功率上限”。这是有物理意义的——配电网给虚拟电厂分配的接入容量是有限的不能无限制地买电或卖电。处理的时候通常加这几条约束购电功率上限正方向、售电功率上限负方向、以及不能同时购售用二元变量锁定。如果简化处理不考虑购售同时的约束结果里可能出现某个时段既买电又卖电的情况——模型通过这种操作“自欺欺人”地凑平衡毫无物理意义。我之前调试的时候遇到过一模一样的坑结果里功率平衡约束完全满足但P_buy和P_sell同时为正。后来加了一对0-1变量把逻辑锁死$$ u_{buy} u_{sell} \leq 1 $$才把这个荒谬操作禁止掉。5.3 可调负荷的建模细节虚拟电厂里的负荷分为固定负荷和可调负荷。固定负荷没法动必须满足可调负荷可以在时间上平移比如工业生产线、电动汽车充电桩。建模时通常用一个“负荷转移量”变量含义是对原始负荷曲线的调整量。这个调整量有两个约束单时段调整量的上下限以及一个调度周期内总调整量为零。第二条的含义是你今天把下午3点的负荷挪到了晚上9点总量不变只是时间平移。如果漏了这条约束模型会把可调负荷当成“无中生有”的资源——缺电的时候砍负荷电多的时候加负荷最后算出来的经济性指标虚高。5.4 求解效率的优化技巧MILP问题的求解速度受整数变量数量影响很大。日前调度模型T24时相对轻松但如果做成96个时段15分钟粒度0-1变量的数量会翻四倍有时求解时间直接爆炸。三个实测有效的优化手段给求解器设置MIP gap一般设为0.1%或1%可以大幅减少求解时间。工程上0.1%的gap损失完全可以接受。把储能充放电状态变量通过逻辑约束简化有时可以去掉一组0-1变量。给所有变量设置合理的上下界不要用默认的无穷大缩小可行域能明显加速求解。6. 常见问题与排查技巧实录复现SCI论文的代码我有过无数次“明明按照论文写的结果却不对”的经历。下面把最常踩的坑和排查方法整理成清单希望帮你少走弯路。故障现象可能原因排查方法求解器报“Infeasible problem”约束条件过强变量上下界冲突先注释掉部分约束逐条加入定位冲突源检查SOC初始值是否在设定范围内结果中储能同时充电和放电缺少互斥约束 u_chu_dis1检查约束列表加入状态互斥逻辑最后时段SOC异常归零缺少末端SOC约束增加SOC(T1)SOC(1)约束日前计划和日内调度结果相差很大日内模型初值传递错误或SOC未衔接打印当前窗口的SOC初始值与日前结果对比求解时间过长0-1变量过多或没有限制变量上下界设置MIP gap添加变量的有限上下界结果经济收益高得离谱可调负荷没有总量守恒约束检查总转移量是否为零6.1 不可行解的排查思路不可行是MILP问题里最折磨人的事情。求解器只告诉你“没有可行解”不告诉你哪条约束出问题。我的经验是不要试图在几百行模型里肉眼看约束太容易漏。正确的做法是逐步缩小问题先只保留功率平衡约束和基本的上下界约束确认这个简版模型可解然后依次加入储能约束、电网交互约束、可调负荷约束、启停互斥约束每加一类跑一次定位到某类约束加入后模型变得不可行再单独细分排查。这个方法基本上能在十分钟内解决问题。我遇到过最隐蔽的一次不可行是储能SOC上限和末端SOC约束的组合初始SOC只有0.3但末端约束要求回到0.3以上而中间某个时段充电功率上限太低导致无法在剩余时间内把SOC充到目标值。单独看每条约束都合理组合在一起却互相打架。6.2 日内调度结果“抖动”怎么处理日内调度因为预测每15分钟更新一次结果可能出现相邻两个调度周期的决策值跳变很大——上一轮决定储能放电下一轮又强力充电。这种抖动在工程上是不可接受的会加速设备磨损。应对方案是给日内模型的决策变量加调整量惩罚项在目标函数里加上当前决策与上一轮决策偏差的二次项或线性项。数学上很直白$$ \lambda_{ramp} |x_t - x_{t-1}| $$λ_ramp是一个调参用的权重系数设太小起不到平滑作用设太大又会让日内调度丧失灵活性。我一般从0.01开始试观察曲线的波动幅度逐步调整到既能跟踪又能保持稳定的状态。这一步在实际项目中很重要但许多复现代码并不会显式写出来。6.3 结果可视化的几个关键图这个项目跑完要验证结果是否合理光看数字不够一定要画图。我常用的图至少有四张功率平衡堆叠图横轴时间纵轴功率用堆叠柱状图展示光伏出力、储能充放电、电网购售电、负荷这几块叠加在一起能正好平衡。这张图在任何汇报里都是第一张。SOC曲线图看储能的电量变化是否符合预期——白天光伏出力大储能充电晚高峰储能放电。如果SOC曲线的变化习性和光伏曲线完全一致说明模型逻辑有问题。日前计划与日内实际对比图两张曲线拼在一起直观展示日内调度修正了什么。收益对比柱状图分别画出确定性方案、场景法方案、鲁棒方案的总收益展示不确定性处理的代价差异。7. 总结与扩展方向复现虚拟电厂多时间尺度调度的项目我个人最大的体会是模型本身并不复杂难的在于对问题本质的理解和调试的耐心。很多初学者一上来就追求模型复杂、约束花哨结果跑出来的数据自己都无法解释。先把日前和日内两个模型的基础版本跑通让所有约束都有物理意义再去加不确定性处理、加碳交易机制、加需求响应这才是稳妥的路径。最后分享一个扩展思路。虚拟电厂是一个发展很快的领域这个项目里的基础框架具备很强的可复用性。比如你可以在日内调度中引入实时市场价格信号做成一个“价格响应型”的调度策略也可以把电动汽车集群作为可调负荷纳入聚合参考当前梯次利用电池、有序充电这类热点概念进一步丰富模型的应用场景。基础框架搭好了加东西其实就是往模型里补变量和约束的事难点反而在于业务逻辑是否想清楚。如果你正准备在这个领域做研究或者做工程落地希望这篇拆解能帮你省下几周摸索的时间。模型和代码的框架思路已经给你铺好路了剩下的就是用你自己的数据去跑、去调、去验证。