考虑储能特性分布的多时间尺度调度Matlab实现
1. 储能接入之后传统调度为什么要改做电力系统调度优化的人应该都有体会过去我们做经济调度核心逻辑其实挺清晰——火电为主力负荷曲线相对可预测机组组合加经济分配基本就能搞定。但新能源渗透率上来之后事情开始变得棘手。风电、光伏的出力波动性强预测误差随时间尺度变化明显源侧的不确定性已经够让人头疼了与此同时负荷侧在电动汽车、空调占比提升后峰谷特性也不再是干干净净的“早晚双峰”。这个时候储能电站作为灵活调节资源接入电网本质上就是往这个混乱的局面里塞进一个“可充可放、响应快但能量有限”的缓冲器。但这里有个很容易被忽略的问题储能电站不是一台标准的发电机组。它的运行状态是时变的SOC荷电状态在充放电循环中不断迁移可用功率上限受SOC区间约束效率特性、功率特性、循环寿命损耗都随工作点变化。很多刚接触这个方向的人会把储能简化成一个“功率可调范围 容量上限”的静态模型这在单一时间断面做优化也许问题不大一旦放到多时间尺度协调里麻烦就来了。标题里提到的“考虑特性分布的储能电站接入”核心就是这个储能自身的时序运行特性、状态分布特性必须被纳入到调度策略的建模中。再叠加“多时间尺度”这个维度意味着日前、日内、实时这几个不同粒度的决策环节要相互衔接偏差要不断修正源侧、储能、负荷三类资源要协同配合。这套东西用Matlab做代码实现是目前很多高校课题组和电网侧研究团队的常见做法也是一个相当经典的综合性优化问题。这篇文章我就以自己实际搭过的模型和踩过的坑为例把这套策略的建模思路、代码架构、求解细节完整拆开讲。2. 数学模型建起来之前先把三个关键问题想清楚2.1 时间尺度怎么划分各阶段分别管什么多时间尺度调度本质上是一种“越靠近执行时刻决策越精细、预测越准确”的分层决策结构。最常见的划分方式是三层日前调度按1小时一个时段覆盖24小时做机组组合和储能充放电的粗计划日内滚动调度按15分钟一个时段向前滚动4到6小时修正日前计划与超短期预测之间的偏差实时反馈控制层按5分钟甚至更短周期基于实时量测数据对执行偏差做最后的调整。三层之间不是孤立的而是通过“计划值传递”衔接起来的。日前给出的机组启停状态和储能SOC轨迹是日内层的可行域边界日内层计算出的修正量是实时层的参考基线。用生活化的方式理解日前层相当于你周末做的旅行攻略日内层是出发当天根据天气和路况调整具体路线实时层则是开车时根据实时红绿灯和车流做的最后微调。攻略不能太死板否则当天没法调整实时调整也不能没有章法否则整体的经济性和安全性就失去了保障。我在实际建模时三层的时间尺度分别取24h/1h、4h/15min、1h/5min滚动优化时日内层每次向前推进15分钟并重新求解——这就是“滚动”的含义。这个参数组合是行业里比较常见的做法不算最优但非常稳妥。如果你在写论文需要体现创新性可以把日内层改成基于模型预测控制方法的形式引入预测时域和控制时域的概念。2.2 储能特性分布到底指什么为什么重要储能特性分布是我认为这个标题里最值得展开的词。它不是说储能电站装在地理上的分布而是指储能运行状态特性的时变分布包括三个层面第一是SOC的分布偏移。同一个储能电站在调度周期内的不同时段SOC可能分布在充电区间、放电区间或者死区。SOC不同可用的上调容量和下调容量就完全不同。比如磷酸铁锂电池SOC在20%到80%区间时充放电功率基本可以按额定值来但SOC接近上限时可充电功率会被管理系统限制接近下限时可放电功率又会受限。如果在日前计划里没有把这个时变特性建模进去日内层一旦发现SOC已经到顶了却还安排充电就会出现执行不了的尴尬局面。第二是功率特性的分布约束。储能变流器的有功输出受P-Q容量约束无功调节时会挤占有功容量。在电网调度里如果储能还承担调压任务这个耦合关系不能忽略。我在模型里用的是简化处理先给定无功计划再计算有功的可行域避免引入非线性。但如果你的场景对无功有更高要求需要把P-Q容量圆约束加进去。第三是效率特性的分布。充放电效率不是恒定值尤其是大倍率充放电时损耗明显上升。很多文章里用固定效率0.95但实测下来0.5C和1C充电的等效效率能差好几个百分点。我的做法是采用分段线性效率曲线把效率建模为充放电功率的分段函数这样既保留精度又不破坏线性模型结构。2.3 源储荷三类资源的协调逻辑“源储荷协调”不是把三类资源简单加到一起优化而是要发挥各自的时间尺度互补性。源侧的火电机组响应慢但出力范围大、持续性强适合承担基荷和腰荷储能响应快但能量有限、成本较高适合做峰谷转移和短时平衡负荷侧的可调节负荷比如温控负荷、充电桩响应快慢不一但调节成本低适合做精细化的削峰填谷。建模时我采用的协调逻辑是日前层以机组启停和储能充放电计划为核心日内层以储能和可调负荷的修正量为核心实时层以储能和火电AGC自动发电控制的快速响应为核心。这个“慢资源定基调、快资源做修正”的逻辑是实践中最稳健的方案也可以说是多时间尺度协调调度最核心的设计哲学。3. 目标函数和约束条件的具体搭建过程3.1 目标函数怎么选经济性指标如何量化目标函数是所有优化模型的心脏。我的做法是采用“全系统运行成本最小化”作为目标包括火电煤耗成本、储能充放电老化成本、弃风弃光惩罚成本、负荷调整补偿成本——这四块放在同一个量纲里比较单位统一折算为费用。火电煤耗成本用二次函数近似这是机组组合问题的标准做法。储能老化成本是很多初做者容易漏掉的项但它恰恰是体现“考虑储能特性”的地方。锂电池的循环寿命与放电深度有强相关关系经常深度充放会显著缩短寿命。我用一个简化模型来建模将每次充放电动作折算成等效循环损耗成本在目标函数中加一个与充放电功率绝对值相关的惩罚项。这样做虽然不如完整的雨流计数法精确但胜在能嵌入线性规划模型计算效率高。弃风弃光惩罚成本和负荷调整补偿成本相对直观。前者用一个大于火电边际成本的价格系数乘以弃电量来表示——之所以这样设置是因为在双碳目标下新能源消纳优先是政策导向惩罚系数要足够大才能让优化结果倾向于消纳后者则是按可调负荷的实际调节量和约定补偿单价来计算。3.2 储能系统的精细化模型方程储能模型是多时间尺度调度策略的核心模块下面这组方程是所有相关代码里的主干。SOC递推方程SOC(t1) SOC(t) Pch(t) * ηch * Δt / E - Pdis(t) * Δt / (E * ηdis)其中Pch和Pdis分别是充电和放电功率ηch和ηdis是效率E是额定容量Δt是时段长度。这里有个细节容易踩坑充放电同时进行的约束必须写死否则优化器会算出一个又充又放的结果来“套利”——虽然目标函数会尽量规避但数值上仍可能出现微小同时充放导致SOC跳变的情况。我在代码里直接用二元变量或者互补约束来禁止。充放电功率约束要分段处理考虑特性分布后功率上限与SOC相关0 ≤ Pch(t) ≤ min(Pch_max, Pch_max_SOC(SOC(t))) 0 ≤ Pdis(t) ≤ min(Pdis_max, Pdis_max_SOC(SOC(t)))其中Pch_max_SOC函数描述了不同SOC区间下可充/可放功率的限制这种约束的加入让储能模型从“理想化”变成了“可执行”的状态。SOC上下限约束SOC_min ≤ SOC(t) ≤ SOC_maxSOC_min和SOC_max通常取0.1和0.9但需要注意为了让储能具备持续的调节能力调度结束时一般要求SOC回到初始位置附近。我在日前模型里加了SOC末时段的回位约束SOC(T) ≥ SOC(0)这保证了储能电站的长期可用性。3.3 火电、负荷、网络侧约束怎么列火电机组的约束是最经典的出力上下限约束、爬坡约束、最小启停时间约束。爬坡约束在多时间尺度模型里要特别注意——日前层按1小时粒度给出的爬坡速率换算到日内15分钟粒度时要除以4。很多模型跑出来爬坡违例都是因为粒度的换算出了问题。负荷侧约束主要是可调负荷的调节能力约束和电量约束。可调负荷的调节量不能超过设定比例比如5%到15%并且调节周期内的总用电量变化有限——空调调高两小时室温和用户体感还在可接受范围内但你不可能让一个工业园区连续8小时全减载。网络侧约束我是用直流潮流模型来做的——忽略无功和网损重点抓线路功率越限。关键方程是节点功率平衡方程和线路潮流容量约束。直流潮流的精度虽然不如交流潮流但在调度策略研究里足够用而且线性结构对求解器的友好程度是碾压级的。要是用交流潮流非线性约束会让整个模型变成MINLP混合整数非线性规划MATLAB里常规工具箱基本算不动。3.4 场景生成与不确定性处理多时间尺度调度的另一个难点是预测误差的模拟。风电和光伏出力预测误差随预测时间尺度变化日前预测误差大日内超短期预测精度明显改善。我的处理方式是使用场景法用蒙特卡洛抽样生成预测误差场景再用K-means聚类或者场景削减技术得到典型场景集。如果你只是想跑通代码而不是做完整的随机优化那么最简单的做法是直接在日内层用一个“误差修正系数”来模拟预测更新。比如日前预测风光出力是100MW日内层的超短期预测变成95MW那么日内层优化时就把风光出力调整为95MW重新计算火电和储能按照新的出力计划去补偿差额。这种处理方式虽然学术深度有限但工程上非常直观也容易实现。4. Matlab代码实现架构、关键代码与求解思路4.1 程序整体架构怎么搭才不容易乱一套完整的多时间尺度调度程序如果全部堆在一个脚本里调试起来会非常痛苦。我的习惯是拆成四个模块数据模块、模型模块、求解模块、结果输出模块。数据模块负责定义系统基础数据——火电机组参数、储能参数、负荷曲线、风光出力曲线、预测误差场景、系统拓扑全部以结构体或表格形式存储。模型模块按照时间尺度分别写出决策变量、目标函数和约束条件。求解模块负责调用Yalmip建模并设置求解器参数。结果输出模块将结果保存为表格绘制SOC轨迹、机组出力、节点电价等曲线。这种架构的好处是换一个测试系统只需要改数据模块换一种求解器只需要改求解模块的调用语句做灵敏度分析只需要循环修改参数重新运行。我在做某跨平台系统的测试时拿到了不同的算例数据只花了半小时就完成了替换和复现。4.2 决策变量如何设计决策变量的定义是整个建模过程中的地基工程。我的设计如下机组组合变量二元变量火电机组在各时段、各场景下的启停状态机组出力变量连续变量火电机组在各时段的有功输出储能充放电状态二元变量标记储能处于充电还是放电状态储能充放电功率连续变量储能充、放电的有功功率值可调负荷调节量连续变量可为正负弃风弃光功率连续变量非负线路潮流量连续变量可由相角变量推导注意一个细节储能充放电状态如果用了二元变量那么充放电功率就不能同时为正。用约束Pch(t) ≤ M * u_ch(t)和Pdis(t) ≤ M * (1 - u_ch(t))来实现互斥其中M是一个足够大的正数比如系统总容量的2倍。在实际求解中如果单纯靠目标函数的惩罚来规避同时充放电经常会出现病态数值所以必须加上显式约束。4.3 Yalmip建模与求解器调用我用了Yalmip作为建模语言求解器用的是商用求解器之一——细说起来这类混合整数线性规划问题对求解器的依赖非常明显我之前试过一些开源求解器算小规模算例还行一旦系统规模上来、时段维度展开求解时间就会成倍增长甚至出现无法在合理时间内收敛的情况。有条件用商用求解器的话建议优先考虑。下面这段代码是多时间尺度模型中日前调度核心建模的最精简版本% 定义决策变量 P_g sdpvar(n_gen, T, full); % 火电出力 u_g binvar(n_gen, T, full); % 机组启停 P_ch sdpvar(1, T, full); % 储能充电功率 P_dis sdpvar(1, T, full); % 储能放电功率 u_s binvar(1, T, full); % 充放电状态 SOC sdpvar(1, T1, full); % 荷电状态 P_curtail sdpvar(n_re, T, full); % 弃风弃光功率 % 目标函数 objective 0; for t 1:T for g 1:n_gen % 煤耗成本二次函数线性化 objective objective cost_coal * P_g(g,t); % 启停成本 objective objective cost_start * max(0, u_g(g,t) - u_g(g,t-1)); end % 储能老化成本 objective objective cost_storage * (P_ch(t) P_dis(t)); % 弃风弃光惩罚 objective objective cost_curtail * sum(P_curtail(:,t)); end % 约束条件 Constraints []; for t 1:T % 功率平衡 Constraints [Constraints, sum(P_g(:,t)) P_dis(t) - P_ch(t) ... sum(P_re(:,t) - P_curtail(:,t)) P_load(t)]; % 储能SOC递推 Constraints [Constraints, SOC(t1) SOC(t) ... P_ch(t)*eta_ch*dt/E - P_dis(t)*dt/(E*eta_dis)]; % 充放电互斥 Constraints [Constraints, P_ch(t) M * u_s(t)]; Constraints [Constraints, P_dis(t) M * (1 - u_s(t))]; % SOC上下限 Constraints [Constraints, SOC_min SOC(t1) SOC_max]; % 机组出力上下限 Constraints [Constraints, P_g_min .* u_g(:,t) P_g(:,t) P_g_max .* u_g(:,t)]; end % 求解 ops sdpsettings(solver, cplex, verbose, 1); optimize(Constraints, objective, ops);注意代码里SOC的索引从1到T1这样SOC(t1)的递推关系直接对应时段t的充放电。这是一个小细节但非常容易搞反。启停成本我用绝对值形式处理了max函数实际代码里需要引入辅助变量把max(0, x)线性化。4.4 多时间尺度之间的数据传递三层模型之间最关键的是数据接口。日前层求解完成后需要把机组启停状态、储能SOC轨迹、各时段出力计划三个结果传递给日内层。日内层在滚动优化时把“机组启停状态”固定为日前层的给定值——因为机组启停动作是大时间尺度决策不可能在日内做频繁调整。储能SOC轨迹则作为软约束参考允许日内层在SOC上做适当偏离但偏离要付出惩罚代价。数据传递用结构体来管理是最清晰的results.day_ahead.unit_status value(u_g); results.day_ahead.SOC_trajectory value(SOC); results.day_ahead.generation_plan value(P_g);日内层读取这些字段作为边界条件再结合超短期预测数据重新求解。实时层类似地读取日内层的计划值。这种结构让三层模型可以独立调试层级关系一目了然。5. 实际调参和踩坑过程实录5.1 求解速度慢怎么办多时间尺度模型最直观的痛点是求解时间。一套包含10台机组、24个时段、考虑储能和可调负荷的日前调度模型用Yalmip加商用求解器求解通常几秒到几十秒能出结果。但如果扩展到日内滚动60个断面每个断面都要重新求解累计的求解时间就会变得非常可观。我的实践经验是三个优化方向一是减少整数变量的数量把机组启停状态只在日前层定义日内层固定不变二是用热启动机制把上一轮优化结果的解作为下一轮滚动优化的初始解求解器能显著减少分支定界的时间三是把可调负荷的整数约束改成连续变量加线性化惩罚虽然略有精度损失但模型从MILP变成LP求解速度能有质的提升。5.2 SOC越界和“计划不可执行”问题这是我调试过程中遇到的最隐蔽的问题。日前层求解出的SOC轨迹看起来非常完美但传到日内层一执行发现有些时段的可充电功率为零但计划却安排了充电。问题出在建模细节上日前层用的SOC可用功率模型是线性化近似日内层用了更精细的非线性映射两边对“SOC接近上限时可充电功率受限”的数学表达不一致。解决方法是统一两层的储能特性分布约束表达形式——都用分段线性函数描述SOC对功率的限制同时在日前层把SOC的运行区间适当收紧比如上限从0.9收紧到0.85给日内层留出安全裕度。这个裕度的取值没有统一标准我一般取5%到10%的SOC区间作为安全余量研究对象不同可以灵活调整。5.3 滚动优化中的振荡问题在日内滚动优化的调试中我遇到了一个有意思的现象储能出力计划在相邻两个滚动断面之间出现大幅交替上一轮计划充电下一轮计划放电再下一轮又充电。系统运行的稳定性受到很大影响储能设备实际没法按这种模式运行。根因在于日内层的目标函数太“短视”——只考虑当前滚动窗口内的成本没有对输出做平滑约束。我的解决方案是加入调节速率约束和计划偏差惩罚要求储能在相邻两个滚动断面之间的出力变化不超过额定功率的20%同时对与日前计划偏差较大的出力给予额外的成本惩罚。加入这两个机制后振荡问题基本消失。5.4 常见问题速查表问题现象可能原因排查与解决办法求解时间过长整数变量过多或者约束冗余减少二元变量维度、设置热启动、优化约束表达SOC越界充放电效率参数不一致统一效率设置UI层与模型层保持一致功率平衡不满足各时间尺度时间粒度未统一检查Δt设置日内层15min换算成小时必须除以4储能计划反复震荡滚动窗口过短无平滑约束加入爬坡约束和计划偏差惩罚求解器返回不可行约束冲突可行域为空逐步放松约束检查优先检查SOC回位约束和机组最小启停时间约束模型最优值与预期差距大目标函数量纲不统一确认所有成本单位统一折算为元/MWh6. 从代码到论文这套框架还能往哪些方向扩展如果你是在校学生做课题或者工程师想在这个方向上做技术预研这套Matlab框架的扩展空间其实很大。我梳理几个我见过或者自己尝试过的方向按难度从低到高排列。第一是储能健康状态的时变建模。把储能特性分布从简单的功率限制扩展到健康状态对容量衰减、内阻增加的影响建立SOH时变模型。这样调度策略就具备了生命周期视角能实现“储能全生命周期成本最优调度”在写论文时这是一个很容易出亮点的创新点。第二是主动配电网层面的源储荷协调。把模型从输电网拓展到配电网要考虑三相不平衡、分布式光伏接入、低压线路电压越限等因素。这个方向在网络约束上要更精细但在算法框架上可以复用已有的三层结构。第三是市场环境下的储能竞价策略。储能电站不仅服务于系统运行还可以参与电力现货市场和辅助服务市场的竞价。这时目标函数从系统成本最小化变为储能运营商的利润最大化博弈对象从“可控资源”变成了“市场出清价格”。这个方向需要引入主从博弈或互补约束或者更精细的市场模型可以把你已有的多时间尺度框架和电力市场出清模型耦合起来。我个人觉得这是储能调度领域这几年最有实践价值的拓展方向。第四是强化学习在实时层的应用。实时层的决策周期短、状态变化快用传统优化方法求解开关频繁的情况下计算压力大。可以把实时层的储能和可调负荷控制建模为马尔可夫决策过程用深度强化学习训练策略网络日内层用优化方法、实时层用学习方法的混合架构是目前学术圈比较前沿的做法。第五是考虑极端天气下的韧性调度。在台风、寒潮等极端场景下输电线路可能大规模故障源储荷协调的目标从经济性优先转为“最小化负荷损失”。储能作为孤岛运行时的应急电源其SOC管理策略需要重新设计。这个方向对模型结构和求解方法的挑战都比较大但研究价值也很高。我个人在实际项目中的体会是不要把扩展方向当成“另起炉灶”而是在原有框架的某个模块里做替换和增强。比如做市场方向就替换目标函数和增加市场出清模块做韧性就替换场景生成模块和改变目标权重。底层的数据结构、决策变量设计、多时间尺度衔接逻辑都是通用的换的是“业务逻辑”不变的是一套清晰稳健的调度决策框架。这也是把Matlab代码实现做实之后最大的资产——框架本身的价值远远超过单次仿真结果的参考意义。最后再分享一个小经验自始至终不要放弃对约束条件的逐条检查。我见过太多人在代码里花了大量精力调求解器参数最后却发现是某个约束的上下限写反了。多时间尺度模型里的约束是分层的任何一层传下去的数据有误最终结果都会偏离物理实际。从第一版代码开始就把每个约束单独跑一遍验证极端条件下约束是否按照预期激活这个习惯能帮你省掉处理结果时最痛苦的排错过程。