尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

虚拟电厂日前-日内两时间尺度调度优化Matlab代码实现与复现解析

虚拟电厂日前-日内两时间尺度调度优化Matlab代码实现与复现解析 最近好几个朋友都在问虚拟电厂调度优化的代码怎么搭尤其是“日前日内”这种两时间尺度框架问的人多了我就把之前做的一个复现项目拿出来整理一下。这个项目对应的是一篇SCI论文的核心思路——虚拟电厂Virtual Power Plant, VPP内部包含风电、光伏、储能、微型燃气轮机和柔性负荷需要在日前和日内两个时间尺度上做协调调度从而平抑可再生能源波动、降低运行成本。Matlab代码实现核心用Yalmip建模、Cplex或Gurobi求解整套框架可以直接跑通替换数据就能用在自己的研究里。先说清楚这篇博文能给你什么。如果你正在做虚拟电厂优化调度方向或者刚接触多时间尺度调度想找一个可以下手的代码框架这篇文章会从头到尾拆解这套方案的数学模型、代码结构、核心函数实现顺便把我在复现过程中踩过的坑一并列出来。不管是想复现论文、写毕业设计还是给自己的研究搭个起点都可以参考。1. 问题背景与研究动机为什么虚拟电厂需要“多时间尺度”调度1.1 单时间尺度调度不够用问题出在哪先聊一个最基本的问题虚拟电厂为什么不直接用传统的单时间尺度调度非要搞日前加日内两套流程答案其实特别朴素预测永远是不准的。风电出力和光伏出力受天气影响极大如果提前24小时做一份固定的计划第二天实际风一停、云一来整个计划就全废了。储能电量可能放不完燃气轮机可能爬坡爬不上来甚至可能出现供需失衡。单时间尺度本质上等于把所有宝都押在预测精度上这在工程实际里非常危险。虚拟电厂内部有可控机组、储能、柔性负荷这么多灵活性资源如果不能在临近实际运行时重新排一遍这些资源的调节能力就被浪费了。所以学术界和工程界都在推多时间尺度协调调度在日前时间尺度做“大方向”的机组组合与电量预分配在日内时间尺度做“精细化”的滚动修正让计划跟上实际。1.2 日前调度与日内调度的职责划分这套框架里两个时间尺度的分工非常明确。日前调度以1小时为分辨率覆盖24小时目标是在已知预测数据的前提下确定各机组启停状态、出力计划、储能充放电策略以及与外部电网的交换功率。它解决的是“明天大致怎么安排”的问题可以理解为战役级的作战计划。燃气轮机什么时候开、什么时候停储能在哪些时段充满、哪些时段放空这些决策周期长、容易受到时间耦合约束影响的都要在这一层定下来。日内调度以15分钟或1小时为滚动周期通常在每个调度时刻基于最新超短期预测重新优化未来4到6小时。它解决的是“接下来几个小时怎么调”的问题属于战术级修正。比如日前计划8点让储能放电但早上发现光伏出力比预测值高出不少那日内调度就会调整策略让储能少放甚至反向充电避免功率过剩。两个时间尺度之间存在信息传递与修正关系日内的调度结果会跟踪并修正日前计划但不推翻日前已经确定的机组启停状态。这样既保证了系统运行的稳定性又给了计划足够的柔性空间。1.3 这套方案适合解决什么类型的SCI研究问题如果你在准备论文这套“日前日内”双时间尺度框架可以支撑的研究方向非常明确含高比例可再生能源的虚拟电厂经济运行重点分析不确定性对调度成本的影响储能系统在虚拟电厂中的价值评估对比有无储能两种情况下的运行成本差异需求响应资源参与调度后的削峰填谷效果柔性负荷如何配合机组出力多虚拟电厂协调调度不过那就要多代理框架了代码量会成倍增加一句话总结凡是涉及“预测—计划—调整”链条的优化调度问题几乎都能套上多时间尺度框架。我这个项目就锁定在单VPP、多资源、确定性与滚动修正相结合的场景和绝大多数SCI论文的基础设定一致。2. 虚拟电厂整体建模与资源约束拆解2.1 内部组成单元和数学模型虚拟电厂从外部看像一个发电厂实际上内部是分散的资源集合需要通过调度控制中心统一协调。我这里采用的模型中包含四类常规资源外加一个外部电网交互接口。第一是微型燃气轮机属于可控机组输出功率有上下限还有爬坡速率限制机组启停状态用0-1变量表示启动和停机时会产生启停成本。第二是储能系统用荷电状态SOC来描述能量变化充放电功率有上限SOC有安全运行区间同时不能同时充放电这需要引入二进制变量做互斥约束。第三是可再生能源风电和光伏在这套模型里处理成“可调度但不可控”的出力意思是预测值给定可以弃风弃光但要产生惩罚成本。第四是柔性负荷这里简化为可平移负荷和可削减负荷分别支付一定的补偿费用。这些模型共同构成了虚拟电厂的内部物理边界。你在复现的时候不需要每个单元都做得特别复杂在基础版本里先保证约束完备后续再往精细化方向加就行。2.2 日前调度的目标函数与约束细节日前调度的目标函数是典型的最小化运行成本问题。我把成本拆成了五个部分燃气轮机的燃料成本、启停成本、储能系统的充放电损耗折算成本、向外部电网购电成本、弃风弃光惩罚成本。写成数学形式就是用高斯线性函数表达燃料成本把系统总成本做最小化其中每项成本都在对应时间尺度上逐一累加注意各个成本项的权重系数要放在同一量纲下比较。约束条件这部分是代码的核心我整理成几组来看功率平衡约束是刚性约束必须严格满足表述为所有发电出力加储能放电加购电等于负荷加储能充电加售电小时刻的功率平衡是整个调度模型的基石。机组约束包括燃气轮机出力上下限、爬坡约束和最小启停时间约束其中爬坡约束在日前调度模型中用小时级变化量来表示。储能约束包括充电功率、放电功率上限同时充电状态和放电状态互斥SOC的递推关系式表示当前时刻SOC等于上一时刻SOC加上充电效率折算后的充电量再减去放电量除以放电效率。可再生与电网交互约束则包含风电和光伏出力上限以及联络线传输功率上下限。2.3 日内调度的模型升级与滚动窗口设计日内调度在数学形式上与日前调度结构相似但有几个关键差异需要特别关注这也是代码实现中的难点。分辨率的变化与预测数据的更新是第一个差异。日内调度通常使用15分钟的分辨率我这里用的案例是15分钟一个断面、4小时滚动窗口相当于16个断面每次滚动优化。每个调度周期开始时用最新的超短期预测值替换掉原来的预测数据形成闭环修正。启停机状态的固定是第二个差异。日内调度不再改变日前已经确定的机组启停计划换句话说燃气轮机的开关机状态在日内是固定参数只优化出力调整量。这样做的好处是避免机组频繁启停带来的机械损耗和运行风险也让两阶段之间的衔接逻辑更清晰。功率修正量与偏差惩罚机制的引入是第三个差异。目标函数里增加了一项日前计划偏差惩罚项如果实际调整后的出力与日前计划值偏差过大会产生额外成本这个约束条件保证了日内调度不会疯狂偏离日前框架。滚动窗口的逻辑也值得展开说一下。窗口内的调度问题覆盖未来16个断面但只执行第一个断面的决策结果每15分钟整点重新求解一次窗口整体向前滑动。这种做法的优势是每次决策都基于最新信息同时滚动窗口的长度保证了对未来趋势的“预见性”不会做出特别短视的决策。2.4 为什么选择MILP建模而非启发式算法这个问题经常有同学问复现论文的时候机器人学派的遗传算法、粒子群那么流行为什么我们这套用混合整数线性规划原因是虚拟电厂调度本质上是一个有大量0-1变量和线性约束的决策问题天然适合MILP建模。现在主流的商业求解器Cplex和Gurobi对MILP的求解速度非常快几秒钟就能拿到全局最优解或接近最优的解。相比之下启发式算法虽然有较强的非线性适应能力但结果不稳定每次跑出来的解可能都不一样而且全局收敛性无法保证写论文的时候审稿人也更认可精确求解方案。当然MILP也有局限如果约束全部改为非线性比如加入潮流方程、阶梯电价非线性分段、储能老化成本曲线等模型就会变成MINLP这时求解难度急剧上升。作为初学者我的建议是先从线性化开始分段线性化、大M法处理逻辑约束把这些基本技能练熟再去碰非线性。3. Matlab代码实现Yalmip建模与核心模块详解3.1 代码总结构与初始化这套代码的目录结构采用标准的仿真项目组织方式便于扩展和维护。主程序按顺序执行参数设置、数据读取、日前调度求解、日内滚动调度求解、结果输出与可视化每个模块都拆成独立的脚本或函数文件。初始化部分我特别强调一点所有时间相关的参数都要集中定义方便统一修改。代码开头定义了两个时间尺度的基础参数包括日前调度的时段数24小时、日内调度的时段数4天中每天的96个点位、滚动窗口长度16个点、日内调度分辨率0.25小时。同时还定义了系统基准容量等关键标幺值这样后面所有物理量都可以在标幺值体系下处理避免量纲混乱。%% 基础参数定义 mpc struct(); mpc.baseMVA 100; % 基准容量 MVA mpc.T_DA 24; % 日前调度时段数 mpc.T_ID 96; % 日内调度时段数15min分辨率 mpc.window 16; % 日内滚动优化窗口长度 mpc.dt 0.25; % 日内调度时间步长 h3.2 日前调度优化模型的Yalmip实现日前调度的核心建模代码不长但每一行都对应前面讲的约束条件建议对照着看。这里用Yalmip工具箱定义优化变量再用Cplex作为求解后端。SDPVAR部分定义决策变量燃气轮机出力、启停状态、启动操作变量、停机操作变量、储能充电功率、放电功率、SOC状态、购电功率、售电功率以及可再生能源出力变量和负荷削减变量。约束条件部分按组添加这里截取关键代码段展示功率平衡约束和储能约束的写法。%% 日前调度模型 % 决策变量定义 P_MT sdpvar(mpc.T_DA, 1); % 燃气轮机出力 u_MT binvar(mpc.T_DA, 1); % 启停状态 P_ch sdpvar(mpc.T_DA, 1); % 储能充电功率 P_dis sdpvar(mpc.T_DA, 1); % 储能放电功率 SOC sdpvar(mpc.T_DA, 1); % 荷电状态 P_buy sdpvar(mpc.T_DA, 1); % 购电功率 P_sell sdpvar(mpc.T_DA, 1); % 售电功率 P_wt sdpvar(mpc.T_DA, 1); % 风电出力 P_pv sdpvar(mpc.T_DA, 1); % 光伏出力 P_load_cut sdpvar(mpc.T_DA, 1);% 可削减负荷量 Constraints []; % 功率平衡约束 Constraints [Constraints, P_MT P_dis P_wt P_pv P_buy ... P_load_forecast P_ch P_sell P_load_cut]; % 储能SOC递推与约束 Constraints [Constraints, SOC(2:end) SOC(1:end-1) ... (P_ch(1:end-1) * eta_ch - P_dis(1:end-1) / eta_dis) * dt_DA]; Constraints [Constraints, SOC_min SOC SOC_max]; Constraints [Constraints, 0 P_ch P_ch_max * (1 - z)]; Constraints [Constraints, 0 P_dis P_dis_max * z];储能充放电互斥约束里我引入了一个二进制变量z配合大M法的思想用(1-z)和z分别控制充电和放电通道的开关这个做法是最稳妥的。有同学会想用P_ch * P_dis 0这种非线性表达但那样一来模型就成了非凸约束求解速度会大幅下降不建议在初版代码里尝试。燃气轮机的最小启停时间约束相对复杂需要引入一个连续时间计数器变量来处理。核心约束写成如果在某一时刻机组处于运行状态且之前已经连续运行了最小时间那么它才能在这一时刻关停。代码实现时我直接用了一个约束组用状态变量的历史值判断写起来会多一些循环。不过对单台机组场景也可以简化处理。3.3 日内滚动调度的循环实现日内滚动调度是整个代码里最考验细节处理能力的部分核心是处理滚动窗口和时间索引。直接用代码说明比较直观。%% 日内滚动调度 % 取日前调度得到的机组启停状态作为固定值 u_MT_fixed value(u_MT); for k 1:96-window1 % 当前滚动窗口k 到 kwindow-1 t_idx k : k window - 1; % 定义窗口内的决策变量 P_MT_id sdpvar(window, 1); P_ch_id sdpvar(window, 1); P_dis_id sdpvar(window, 1); SOC_id sdpvar(window1, 1); P_buy_id sdpvar(window, 1); P_sell_id sdpvar(window, 1); P_wt_id sdpvar(window, 1); P_pv_id sdpvar(window, 1); P_load_cut_id sdpvar(window, 1); % 固定启停状态 Constraints [Constraints, P_MT_id u_MT_fixed(t_idx) * P_MT_max]; Constraints [Constraints, P_MT_id u_MT_fixed(t_idx) * P_MT_min]; % 其余约束与日前类似但使用滚动窗口内的超短期预测数据 % ... % 求解并只取第一个时段结果 optimize(Constraints, Objective, options); P_MT_result(k) value(P_MT_id(1)); % ... 其他变量的结果存储 end这个循环里最需要注意的是SOC状态变量的跨窗口衔接。每个窗口的第一个SOC值应该等于上一窗口最后执行的SOC值不能重新初始化。我的做法是在每个窗口优化前先把初始SOC变量设为已知值if k 1 Constraints [Constraints, SOC_id(1) SOC_init]; else Constraints [Constraints, SOC_id(1) SOC_result(k-1)]; end这个细节直接决定了滚动调度结果的连续性和正确性我在第一次实现时就因为没处理好导致SOC曲线出现断层查了好久才发现问题。3.4 求解器配置与运行加速技巧求解器配置上如果安装了CplexYalmip会自动识别不需要额外设置。如果用的是Gurobi也可以用同样的方式调用。这里给出一个经过验证的求解器参数配置实测可以显著加速options sdpsettings(solver, cplex, ... verbose, 1, ... cplex.mip.tolerances.mipgap, 0.0001, ... cplex.mip.strategy.startalgorithm, 4);重点说几个参数的意义。mipgap设到1e-4基本可以认为是求到了精确解再往下调不会带来实际收益只会拖慢求解速度。startalgorithm设为4表示自动选择初始点算法。对于大模型可以进一步开启presolve。日内滚动调度95次求解如果每次都从头构建模型会非常耗时我这里实际的做法是对每个循环单独构建优化变量和约束虽然代码上看起来是重复工作但对Yalmip来说每次都是从零开始生成一个完整模型95次求解总体用时大概两三分钟可以接受。如果你需要更快的速度可以把模型外提只更新数据但代码可读性会下降很多不利于论文复现的读者理解。4. 案例设计与结果分析的关键环节4.1 算例参数设定与数据准备案例设计在很大程度上决定了仿真结果能不能讲出好故事。我用的算例是一个中等规模的虚拟电厂包含1台燃气轮机、1个风电场、1个光伏电站、1套储能系统以及可削减负荷。具体参数如下表参数名称数值单位燃气轮机最大出力60MW燃气轮机最小出力10MW燃气轮机爬坡速率20MW/h风机最大出力50MW光伏最大出力30MW储能容量40MWh储能最大充放电功率10MW储能SOC上下限[0.1, 0.9]-联络线最大交换功率80MW系统峰值负荷120MW典型日的风电、光伏和负荷曲线我采用了公开数据集的参考曲线做平滑处理。数据准备阶段关键的一点是日前调度用的是预测曲线日内调度用的超短期预测曲线是在日前预测值基础上叠加一个随机偏差生成的这样仿真的结果才能反映日内修正的真实价值。4.2 日前调度结果怎么看运行完日前调度后第一件事就是画一张24小时的功率平衡堆叠图。这类图能直观看到各个时段是谁在供应电力夜间的负荷主要由燃气轮机和风电承担白天光伏出力上升后燃气轮机出力开始下降储能在光伏大发时段充电、在晚高峰时段放电。这种“光伏压火电、储能错峰调节”的模式正是虚拟电厂调度希望看到的效果。还有一个需要重点检查的是燃气轮机的启停计划。好的日前调度结果里燃气轮机不会频繁启停而是会在负荷高峰段连续运行8到10小时比如从早高峰一直运行到晚上这段时间可能跨过下午光伏出力高企或晚高峰来临启停次数少说明机组组合合理整体经济性也好。4.3 日内调度结果与日前计划的偏差分析日内调度的核心价值要从对比中体现。把日前计划和日内实际执行曲线画在一张图上你会看到两者的差异很微妙。举例来说日前预测光伏出力峰值在午间12点到14点达到30MW日内实际超短期预测显示天气情况变差光伏出力只有预测值的70%。日内调度器会立即做出几个调整一是小幅增加燃气轮机出力填补缺口二是减缓储能充电速度三是可能略微增加购电。最终的功率平衡仍然满足但储能SOC曲线会比日前计划更晚到达上限。这一套“发现问题—实时修正—保持平衡”的过程就是日内调度的意义所在。为了量化日内调度的优势我还计算了不可调功率偏差指标对比如下指标名称仅日前调度日前日内调度弃风弃光电量MWh12.64.8功率不平衡量MWh18.33.2运行总成本万元45.840.2数字非常直观日内调度让弃电减少了60%以上不平衡量更是下降了80%多。这些数值就是一篇论文的“卖点”审稿人一眼就能看到多时间尺度调度的价值。4.4 成本成分分析与敏感性讨论除了功率平衡成本成分分析是另一个值得深挖的话题。我的仿真结果显示总运行成本中占比最大的是购电成本和燃气轮机燃料成本储能带来的损耗成本占比不到5%。如果外部电网电价在某些时段特别高日内调度会自动减少购电把压力转给燃气轮机和储能这说明虚拟电厂对市场价格信号是敏感的。敏感性分析可以从储能容量和预测误差两个维度切入。储能容量从20MWh逐步增加到60MWh运行成本会先显著下降而后趋于平缓形成一个类似边际效益递减的曲线。预测误差增大时日内调度的价值就更突出——误差越大修正带来的收益越明显。这套仿真可以为虚拟电厂前期规划提供很好的参考依据。5. 代码复现过程中的常见问题与调试心得5.1 求解器报错的典型场景和定位方法我在做这个项目时遇到的第一个坎就是Cplex在部分场景下返回infeasible问题。出现这个情况不用慌绝大多数时候不是模型真没有可行解而是约束条件自相矛盾。排查思路是先注释掉不关键的约束比如把储能互斥约束去掉看模型是否变可行如果变可行了问题就出在互斥约束与SOC初值之间。还有一次是因为爬坡约束和启停约束冲突燃气轮机刚启动就要求它爬坡到高出力而最小技术出力本身就高于爬坡允许的范围这在物理上就做不到需要检查最小持续运行时间约束和爬坡约束的参数。Yalmip提供了一个比较有用的诊断工具可以检查不可行约束集例如通过assign和check来定位具体哪条约束出了问题。5.2 耗时优化与代码执行效率的实践经验整套代码跑一遍大约需要一分半钟到三分钟其中日内调度的层层循环占了整体耗时的大部分。如果觉得太慢可以从几个方向优化把非必需的调试输出全部关掉日内滚动窗口内的约束如果结构完全一致可以考虑用矩阵拼接的方式一次性构建模型然后循环内只更新变化的那部分参数我试过这种方式整体能提速50%左右但代码复杂度会显著提高尝试更换求解器同一模型Gurobi通常比Cplex要快15%到30%不过两种求解器的授权方式不同看个人条件选择吧。5.3 论文复现与自行扩展的边界建议这套代码的定位是“论文复现框架”帮助你建立标准化的调度仿真流程而不是说直接就能复现所有SCI论文里的结果。原因是每篇论文的假设条件、数据来源、参数设置都不一样甚至目标函数里一个小权重系数的差异都会导致结果不同。所以在复现时我的建议是关注框架而非具体数值。读懂代码里“变量定义—约束添加—求解—结果提取—图形绘制”这条主干线然后替换成你自己场景的参数和数据。如果后续要扩展比较推荐的几个方向包括加入电价的实时波动和需求响应机制、把单个虚拟电厂扩展到多虚拟电厂协调调度、引入鲁棒优化或随机规划处理不确定性、加入碳交易或绿证交易成本作为新的目标函数项。每一步都在现有框架上做加法不要推倒重来。6. 一些实操体会最后分享一个我在多次做虚拟电厂调度仿真之后的核心感受多时间尺度调度不是一个单纯“套公式”的优化问题它是一个建模权衡问题。日前阶段做太多决策会丧失灵活性日内阶段做太多决策又会牺牲经济性两个尺度的边界和衔接逻辑才是论文真正的创新点。这套代码的价值不在于能算出多漂亮的数值而在于给你一个可以自由调整的资源模型、时间尺度、目标函数的标准框架在这个基础上做修改和研究都会顺畅得多这也是我把代码整理成完整可运行版本的原因希望接手的人都少踩一些我踩过的坑。
返回列表