综合能源系统两阶段调度:日前日内优化模型与Matlab实现
最近一直在做园区级综合能源系统的优化调度课题手头这套“日前-日内两阶段调度”的Matlab程序从建模到调参折腾了小两个月中间踩了不少坑也有不少值得记录的经验。趁着项目告一段落把这套方法完整梳理一遍给正在做微电网、冷热电联供、储能优化配置或者综合能源系统调度方向的同学做一个参考。这篇文章不是教科书式的算子推导而是从“拿到一个题目后怎么一步步落地”的角度说清楚数学模型怎么建、Matlab代码怎么写、求解器怎么配、算例结果怎么解读最后把那些容易让人抓狂的报错和异常也一并整理出来。1. 一上来先聊清楚为什么非要“日前日内”两阶段调度1.1 单阶段方案为什么在工程落地时不够用很多刚接触综合能源系统调度的人第一反应是用一个单层优化模型解决全部问题给一个目标函数把约束加上扔给求解器输出一组“最优”的机组出力和储能充放电计划。这种做法在论文复现和小规模验证中确实可行尤其当预测数据完全准确时单阶段最优解就是理论上限。但实际工程里有一个绕不开的问题预测永远不完美。风电和光伏发电受天气影响波动很大负荷侧也充满不确定性如果把所有决策都压在一个时间断面或者一个确定性的预测序列上一旦实际值偏离预测值整个计划就得推翻重来。单阶段模型缺乏“滚动修正”机制对误差的容忍度很低尤其是储能设备这种带时序耦合的单元一个时间点的偏差会沿时间轴向后传导导致后续所有时段的充放电策略全部失真。1.2 两阶段框架的协作逻辑先粗后细、逐级修正两阶段调度的核心思想是“分层决策、逐级逼近”。日前调度阶段以1小时或者更长为时间尺度基于未来24小时的预测数据制定各机组启停状态、储能充放电基点、与外网购电曲线的“预计划”日内调度阶段则以更短的时间尺度如15分钟或5分钟滚动运行在日前计划基础上只对可控设备进行增量修正用来吸收预测误差带来的功率偏差。打个比方日前调度相当于做年度预算把大方向定下来日内调度相当于每月滚动复盘根据实际收入和支出调整下个月的花钱节奏。这种“长周期决策定结构、短周期调整补偏差”的思路能同时兼顾全局最优性和实时响应性也正是综合能源系统这种强耦合、强不确定对象的工程化方案中常见的处理模式。1.3 为什么选择 Matlab 作为实现载体做这个方向的可选工具其实不少Python生态里有Pyomo、Gurobi的Python接口也有直接调Cplex的写法Matlab则胜在矩阵化思维和工程调试的便利性。一个典型的综合能源系统优化问题目标函数和约束条件天然适合用矩阵向量表示Matlab的Yalmip工具实际用下来对这个问题非常友好建模基本与数学公式一一对应不用像纯写求解器API那样手动维护大量索引矩阵。另外Matlab的可视化能力做调度结果分析很方便日前计划曲线、储能SOC轨迹、购电功率对比都能很快画出比较规范的图。对于研究生课题和工程项目验证来说Matlab这套组合基本够用入门门槛也相对友好。2. 数学模型搭建把物理系统翻译成优化问题2.1 典型园区综合能源系统的物理结构先定义一个标准算例结构后面所有代码和结果都以此为基础。假设园区内包含风力发电机WT、光伏阵列PV、燃气轮机GT、电储能BESS、电锅炉EB系统与外部配电网相连可以购电也可以在某些约束下考虑余电上网。电负荷由风电、光伏、燃气轮机、储能放电、电网购电共同满足燃气轮机余热回收附带满足一部分热负荷不足部分由电锅炉补足。为了让问题可解但不失代表性这个模型忽略详细的网络潮流约束只考虑功率平衡和机组技术约束属于典型的能量枢纽型经济调度模型。这也是目前相关方向的主流简化方式——先保证能量平衡和时序耦合关系正确再考虑网络拓扑扩展。2.2 目标函数经济性优先兼顾运行惩罚日前调度的目标函数取系统总运行成本最小包括与外部电网的购电费用、燃气轮机的燃料成本、储能充放电的折旧维护成本以及可能存在的弃风弃光惩罚这个惩罚项很重要——因为如果不加惩罚优化器从纯经济角度出发可能宁可弃掉价格较低时段的富余新能源造成清洁能源浪费。目标函数写成min F sum(购电电价(t) * Pgrid_buy(t) - 售电电价(t) * Pgrid_sell(t)) sum(燃气成本系数 * Pgt(t)) sum(储能单位损耗成本 * (Pch(t) Pdis(t))) sum(弃风弃光惩罚系数 * 弃风弃光功率(t))其中 t 遍历调度时段日前阶段为24个时段、每个时段1小时日内阶段按滚动窗口求解例如每15分钟一个时段、预测窗口4小时即16个点一个决策窗口。日内阶段的目标函数则在日前计划基础上引入“跟踪偏差惩罚”即对设备出力偏离日前计划的部分施加二次项或线性惩罚。这样日内不是完全重新优化而是尽量贴着日前计划走只有供需偏差明显时才会调整避免设备出力频繁波动。2.3 约束条件等式约束与不等式约束的完整拆解约束分四类。第一类是电功率平衡约束每个时段风电、光伏、燃气轮机、储能放电、外网购电之和等于电负荷、储能充电、电锅炉用电、外网售电之和。热负荷平衡约束同理由燃气轮机余热和电锅炉供热共同满足。第二类是设备出力上下限约束各机组出力在额定范围之内储能充放电功率不得大于额定功率且不能同时充电和放电这个用二元变量或者“互斥约束”来实现Yalmip里可以用binvar配合不等式处理也可以对两个连续变量加 Pch * Pdis 0不过在连续线性规划中要慎用乘积形式建议用二元变量避免非线性。第三类是储能时序递推约束SOC(t1) SOC(t) 充电效率 * Pch(t) * dt / 容量 - 放电效率 * Pdis(t) * dt / 容量同时SOC要在安全区间内起始和末尾SOC要设定约束保证调度周期的能量连续性。第四类是爬坡约束燃气轮机相邻时段出力变化幅度有限制这是最容易被初学者漏掉的约束一旦漏掉求解器给的GT出力变化率会非常夸张造成结果虽然在数学上可行、物理上却完全无法执行。2.4 不确定性建模场景法与预测误差模拟综合能源系统的源荷预测误差普遍存在工程上常用场景法处理用历史统计规律生成多组预测误差场景两阶段随机优化在这组场景上求期望最优。不过随机优化在求解规模和代码复杂度的代价不小。实际上更常见也更实用的处理方式是在日前阶段做确定性优化在日内阶段通过滚动优化来抵消误差然后对预测误差的统计特性做灵敏度分析。在Matlab中生成预测序列时常见做法是用基础预测曲线叠加高斯噪声。以光伏预测为例先根据光照强度计算理论出力再按预测误差标准差生成扰动。温度、云量等因素可以后续加进来但第一版模型建议不要一步到位否则排查问题时分不清是模型错误还是数据错误。这里特别推荐把预测误差设置为随预测时长增大而增大的非平稳噪声更贴近真实情况也能让日内调度的修正幅度更客观不至于被过大的扰动或过小的扰动带偏算例结论。3. Matlab 实现全流程从杂乱数据到调度结果3.1 开发环境与求解器选型Matlab下做优化调度目前最顺手的组合是Yalmip Cplex/Gurobi。Yalmip是一个建模层不用手动把问题转成标准型只需用sdpvar定义变量、用约束符号写约束、用optimize调用求解器。Cplex和Gurobi是底层求解器处理混合整数线性规划性能非常强。如果暂时没有商业求解器直接用Matlab自带的linprog和intlinprog也能跑通小规模算例但求解速度和大规模验证体验会差不少。另外还有fmincon可用于非线性问题但综合能源调度这类问题如果能保持线性含整数变量就尽量不要走非线性路线因为非线性求解器的收敛性、初值敏感性都会浪费大量调参时间。3.2 数据生成与输入模板电价、负荷、风电光伏预测值把所有输入数据集中在一个结构体里很管用比如用data.time表示时段序列data.load表示负荷预测data.wind和data.pv表示新能源预测data.price表示分时电价。这样在写模型时可以直接引用不用在代码里到处改数字也方便做多场景对比。电价通常采用分时电价谷段、平段、峰段各有不同的购电价格售电价一般取购电价的折扣比例。分时电价是驱动储能“低充高放”的直接原因设置不合理会导致储能策略反常——比如峰值电价与谷值电价差过小时储能套利收益无法覆盖损耗成本优化结果可能干脆不用储能这算例就失去意义了。负荷、风电、光伏预测值可以来自实测数据也可以用曲线加噪声模拟只要确保数据量纲一致、时刻对齐即可。建议所有有功功率统一用MW或kW时间单位统一用h避免换算错误。3.3 优化模型落地Yalmip 建模代码详解下面给出一个简版但结构完整的代码框架核心是演示变量定义、约束写入和求解调用。实际项目中只需要在此基础上扩充设备类型和约束细节即可。% 定义变量 Pgt sdpvar(1, 24); % 燃气轮机出力 Pch sdpvar(1, 24); % 储能充电功率 Pdis sdpvar(1, 24); % 储能放电功率 SOC sdpvar(1, 25); % 储能荷电状态含初始和末端 Pgrid sdpvar(1, 24); % 外网购电正为购电负为售电 u_ch binvar(1, 24); % 充电状态标志 u_dis binvar(1, 24); % 放电状态标志 % 目标函数 objective sum(price_buy .* max(Pgrid, 0) - price_sell .* max(-Pgrid, 0)) ... gas_cost * sum(Pgt) battery_cost * sum(Pch Pdis) ... penalty * sum(curtail_wind curtail_pv); % 约束集合 C []; % 功率平衡 C [C, Pwind - curtail_wind Ppv - curtail_pv Pgt Pdis Pgrid load Pch P_eb]; % 设备上下限 C [C, 0 Pgt Pgt_max, 0 Pch Pch_max, 0 Pdis Pdis_max]; % 储能互斥约束 C [C, Pch M * u_ch, Pdis M * u_dis, u_ch u_dis 1]; % SOC递推 C [C, SOC(2:25) SOC(1:24) eta_ch * Pch - Pdis / eta_dis]; C [C, SOC_min SOC SOC_max, SOC(1) SOC_0, SOC(25) SOC_end]; % 求解 ops sdpsettings(solver, cplex, verbose, 2, showprogress, 1); result optimize(C, objective, ops);一个大数 M 的取值很有讲究过大会导致数值稳定性变差过小则可能得到不可行解一般取该设备额定功率的5到10倍即可。此外 max(Pgrid,0) 这种写法在Yalmip里其实不建议直接使用更好的做法是引入两个非负变量 Pbuy 和 Psell用 Pgrid Pbuy - Psell 替代这样模型仍然保持线性也不会引入不可微点。这是把代码从“看着能跑”优化到“大规模可稳定运行”的关键一步。3.4 求解后处理与可视化求解完成后最需要检查的是求解状态输出在Matlab中可以通过result.problem 0判断求解成功problem为1时表示不可行需要检查约束是否矛盾。Cplex的退出信息里也会给出不可行约束诊断这在模型排错时帮助非常大。可视化部分推荐画三类图一是各时段电功率平衡堆叠图用bar或者area显示风电、光伏、燃气轮机、储能和购电各自的贡献二是储能SOC和充放电功率曲线检验是否遵循“谷充峰放”的规律三是日前计划与日内修正的对比曲线体现两阶段调度的时域修正差异。图直接导出为矢量图放到论文或报告里比较清晰。4. 算例结果分析日前计划怎么定、日内滚动怎么修4.1 算例参数与系统规模为使演示结果有参考价值这里设置一个典型算例系统额定容量为风电30MW光伏20MW燃气轮机15MW储能10MW/40MWh电锅炉8MW。负荷峰值为45MW分时电价设置为峰段1.2元/kWh、平段0.8元/kWh、谷段0.4元/kWh燃气轮机燃料成本折算约0.6元/kWh。所有参数都只是示范值但整体数量级与常见园区级系统是匹配的。日内阶段滚动窗口取未来4小时时间步长15分钟即每个窗口16个点每15分钟重新优化一次只执行当前第一个时段的控制指令然后向前滚动。预测误差统一按预测值乘一定比例的高斯扰动处理。4.2 日前调度结果储能“低充高放”与购电曲线分析在分时电价驱动下日前调度结果的储能曲线非常典型谷电时段比如夜间23点到次日6点储能充电SOC从20%逐步上升至90%左右峰电时段比如白天10点到15点储能放电SOC回落至30%左右。风电在夜间出力大、负荷低多出的电量给储能充电或直接供给电锅炉供热弃风比例被压缩到很低水平。购电曲线呈现出“谷段购电多、峰段购电少”的形态整体经济性优于无储能方案。从这个结果能明显看出日前调度阶段的核心价值是充分利用分时电价的时间套利空间同时合理安排燃气轮机的运行区间避免高效率区间和高峰负荷时段发生冲突。4.3 日内修正过程滚动时域如何纠正预测偏差日内阶段引入实际风电出力与预测值的偏差后滚动优化的修正效果体现在以下几个地方。如果实际风电比预测大系统会优先减少外网购电同时适当减少燃气轮机出力多余电量给储能充电或加大电锅炉供热如果实际负荷高于预测系统会增加购电或调整储能放电计划而燃气轮机的调节幅度则受爬坡约束限制。观察SOC曲线时会发现日内阶段并不严格复制日前SOC轨迹而是会在日前的基础上小幅偏移。这是因为日内优化窗口只有4小时看不到全天的总信息只能做局部最优修正。这正是两阶段调度的本质——日前看全局、日内看局部各管一摊又互相配合。4.4 两阶段协同效果对比把两阶段方案与单纯日前开环控制对比即日前计划直接执行不做日内修正在预测误差标准差为15%的场景下两阶段方案的综合运行成本能降低6%到10%同时系统功率不平衡量明显减小。这个数字对应不同参数会有波动但趋势是稳定的预测不确定性越大日内滚动的相对价值越高。这里额外说明一点两阶段方案的提升并不只是“成本降了多少”更关键的是系统应对突发偏差的鲁棒性。日内修正让设备实际出力始终贴近系统真实需求不容易出现某时段严重功率缺额或者严重弃电的情况。用鲁棒性指标比如最大不平衡量、偏差平方和来衡量差异会更突出。5. 常见报错与排查经验速查5.1 约束矩阵维度不匹配的经典报错刚写模型时最常遇到的报错是“Dimensions mismatch”或者“Inconsistent dimensions”。原因绝大多数是变量长度定义不一致比如日前阶段设了24个时段但某个约束里用到的常数向量还是48个点或者SOC变量定义为1×25但递推约束里索引写成1×24对应不上。这类问题没有捷径只能逐个约束用size()检查变量和常量的维度。建议从一开始就把所有常量向量统一生成后再进入建模阶段别在约束里临时计算。5.2 求解器模型构建与调用问题如果已经安装了Cplex或Gurobi但optimize直接报找不到求解器十有八九是Yalmip的求解器路径没有正确配置。在Matlab命令行运行yalmiptest可以查看已安装求解器的状态。另外一种情况是模型中含有非线性项比如两个变量相乘而默认求解器是线性求解器Yalmip会报“No suitable solver”。处理方式是检查模型里是否有Pch与Pdis相乘的约束或者是否误把预测值也定义成了sdpvar变量。还有个小坑是二元变量和连续变量混合时如果约束是大M约束M取太小导致问题不可行Cplex会返回“Infeasible”如果M取太大又可能出现数值病态。这种事没有统一标准只能根据设备功率量级拍一个合理值或者用分段枚举方式规避大M约束。5.3 预测误差序列不合理导致的求解失败一种隐蔽的问题是预测误差慢慢累积导致计算得到的SOC长期处于极端状态某时刻出现“SOC低于下限但负荷仍需要储能放电”的矛盾约束。很多新手以为是模型问题实际是输入数据不合理。解决办法是检查生成的新能源预测序列是不是出现过长的连续低估或高估必要时可以将误差均值设置为零、限制误差的上下边界保证误差序列具备合理性。还有一种情况是采用“所有场景误差方向一致”的模拟这会让某时段购电需求极端偏高进而触发购电上限约束最终导致模型不可行。对这种问题可以通过增加外网购电上限或让燃气轮机留有备用容量来缓解。预测误差序列不是随机就好至少要符合物理逻辑。5.4 其他工程化建议和避坑经验有几个经验值得说一下。第一不要急于堆复杂度。第一版模型建议先做纯电系统加储能连热负荷都不要加跑通后再逐步增加燃气轮机、电锅炉和热平衡约束否则出错了根本不知道问题出在哪一层。第二单位制要统一好。如果输入负荷单位是kW储能容量单位和时段步长也都要对应别混用kW和MW这种错误尤其隐蔽——结果看起来不夸张但数值全偏。第三图上看到的曲线如果出现剧烈振荡先不要怀疑求解器。先在目标函数里检查是否有自由变量没有成本项比如某些设备的出力项没有进目标函数优化器就可以随意取值导致结果看似合理实则震荡。第四把求解信息记录下来。每次运行保存求解状态、目标值、求解耗时、约束数、变量数方便横向对比不同参数的影响。第五也是我个人觉得最有用的一点改参数时一次只改一个。两阶段调度模型涉及参数太多如果同时调整电价、储能容量和预测误差结果变化了也无法知道是哪一个原因导致的复盘时特别混乱。这个项目做到后面我对两阶段调度的理解早已不只是“跑出来一条最优曲线”而是体会到它本质上是对不确定性的工程妥协。Matlab加Yalmip这套组合在建模效率上确实帮了大忙遇到大规模算例时模型的表达式和数学公式几乎一一对应排错和迭代要轻松很多。如果你也在做综合能源系统相关课题建议从最简单的微电网模型开始把日前日内框架和滚动优化逻辑先吃透再做冷热电联供或多能互补扩展。这套代码结构和建模思路不管后面换什么求解器或者扩展到哪种能源形式底层的分层决策思想都能沿用。后续我还会把碳交易机制、需求响应和储能寿命衰减模型往这套框架里加等这几个模块跑出更完整的算例再继续分享。