
做储能项目的都知道调峰和调频这两件事单拎出来每个都有成熟的优化模型但真要把储能同时扔进两个市场里跑联合优化坑比想象中多得多。最近把一套“储能参与调峰调频联合优化模型”的MATLAB代码完整跑通并做了几个场景的压力测试这里把整个建模思路、代码架构、参数整定和调试过程中踩过的坑一次性说清楚。这篇东西适合正在做储能经济性评估、电力市场仿真或者写相关方向硕博论文的朋友尤其是那些已经有了基础优化模型、想进一步做联合运行策略但不知道怎么下手的人。这个模型本质上解决的是一个典型的多时间尺度耦合决策问题调峰市场需要储能长时间尺度的能量搬移比如低谷充电、高峰放电时间尺度是小时级调频市场需要储能秒级到分钟级的快速功率响应用来跟踪AGC指令。两个市场对储能的需求在时间尺度和功率特性上是冲突的如果分开优化结果往往是调峰收益算得很漂亮但调频性能指标一塌糊涂反过来也一样。联合优化的核心就是在同一个优化框架下把两个市场的收益和成本统一建模通过充放电策略的协同找到一个全局最优的功率分配序列。我用的求解框架是MATLAB YALMIP CPLEX这套组合在电力系统优化领域算是标配了。YALMIP负责建模CPLEX负责求解混合整数线性规划MATLAB负责前后处理和结果可视化。下面按实际做项目的顺序把每个环节的关键点拆开讲。1. 模型设计与数学建模1.1 为什么必须做联合优化而非顺序优化很多人一开始会想既然调峰和调频是两个市场那我先算调峰的最优充放电计划再在这个计划的基础上叠加调频功率不就行了这个思路听起来没毛病实际跑下来会发现两个致命问题。第一个问题是调频功率的叠加会导致调峰计划失真。调峰市场的功率基准是日前申报的如果实际运行中叠加了调频功率那么实际充放电功率会偏离申报曲线。在电力市场环境下偏离意味着偏差考核收益会被扣掉一块甚至可能被考核成负数。第二个问题是电池SOC是强耦合的调频动作会不断改变SOC导致按调峰计划预先设定好的SOC轨迹完全失效。我实测过一个场景单独优化调峰时SOC在高峰时段能放到20%左右叠加上调频动作后实际运行中SOC在高峰时段可能已经提前跌破下限导致调峰放电能力不足整个收益模型崩掉。联合优化的本质是把调峰功率和调频功率作为同一个决策变量体系下的两个分量来建模两个市场的收益同时进目标函数所有物理约束同时作用于总功率和SOC这样优化器自己会找到平衡点。跑出来的结果往往是调峰计划略微保守腾出一部分功率和容量空间给调频整体收益反而更高因为调频的单位收益通常远高于调峰。1.2 目标函数拆解这个模型的目标函数是最大化储能系统在一个优化周期内的综合收益。我用的优化周期是24小时时间间隔15分钟也就是96个时段。目标函数长这样目标 调峰收益 调频收益 - 充放电损耗成本 - 寿命衰减成本 - 偏差考核成本调峰收益这块比较直接就是每个时段的调峰功率乘以调峰电价再乘以时间间隔。调峰功率我把它定义成储能的充放电功率基准值放电为正、充电为负。调频收益分两部分容量收益和里程收益。容量收益是储能被调频市场占用的功率或容量乘以调频容量价格这部分只要中标就能拿到和实际动作多少无关。里程收益是实际调频动作量的函数我用的结算规则是里程价格乘以实际调频里程。这里有一个建模细节很多人会忽略调频功率是双向波动的向上调节和向下调节对应的里程要分别统计但在优化模型里通常用对称的调频功率上限来近似。实测中这种近似对最终收益的影响小于5%但能显著降低模型复杂度值得做。充放电损耗成本我用的是线性化的充放电效率模型。电池充电效率0.95放电效率0.95综合效率约0.9。在目标函数里直接对充放电功率乘效率系数求积分代表能量损耗对应的成本。严格的做法是分时段非线性建模但MILP里这样做会把模型推向非线性求解速度慢到无法接受。实际工程中线性化损失可以接受。寿命衰减成本是这两年储能经济性评估里越来越重要的项。我一直用的方式是建立循环次数和放电深度的关系然后折算到每MWh的吞吐成本。简单做法是直接对每个时段的放电电量乘一个寿命损耗系数。如果数据足够可以做更精细的DOD-SOC耦合衰减模型但作为通用框架固定系数法已经够用。偏差考核成本是联合优化的关键。它惩罚实际总功率和调峰申报功率之间的偏差。这个惩罚系数的设置直接影响优化结果的行为——惩罚系数设得太小模型会无视调峰申报曲线拼命做调频设得太大调频收益全部被吃掉联合优化退化成纯调峰。我的经验是把这个系数标定到大概等于调峰电价的两倍结果比较合理。1.3 约束条件体系约束条件是这个模型里最需要花心思的地方每个约束都有坑。SOC动态约束是基础。SOC(k1) SOC(k) 充电功率×充电效率×Δt / E - 放电功率×Δt / (放电效率×E)。这里注意充放电两个方向效率处理不同很多初稿模型在这里偷懒用一个效率跑出来的SOC轨迹会系统性偏低或偏高。我自己的代码里是把充电功率和放电功率分别设成非负变量然后各自乘对应效率不要合并成一个有符号变量这样可以天然规避效率方向错误的问题。功率约束包括总功率上限约束充电功率和调频功率的某个组合必须落在额定功率范围内。正确写法是[充电功率 调频功率上限] 额定功率以及[放电功率 调频功率上限] 额定功率。这里有个细节调频功率是双向的保守做法是上下各留出调频容量即充电方向留调频容量、放电方向也留调频容量。也就是说储能的额定功率要同时覆盖“充电功率向上调频”和“放电功率向下调频”这两种极端情况。很多论文里写的是功率上限约束但没有明确区分两个方向实际是错的。SOC上下限约束比较直接但要注意两点。一是为了防止模型把SOC用到极端值导致寿命衰减过快我通常把运行SOC限制在10%到90%而不是电池本身的0到100%。二是调频动作对SOC的影响需要在约束里体现。一种做法是把调频对SOC的影响放在调频里程的期望值上用期望里程来估算SOC偏移量确保SOC轨迹在调频扰动下不会越界。这个约束写起来麻烦但很关键不写的话最优解在实际运行中会因为调频动作导致SOC越界策略不可执行。爬坡约束经常被忽略。储能虽然响应速度快但实际PCS的功率变化率还是有上限的15分钟间隔的调度模型里爬坡约束通常不起作用但如果优化间隔缩短到分钟级爬坡约束必须加上。我这个模型用的96时段调度间隔爬坡约束基本是松弛的但代码里还是保留了用来应对未来改成分钟级优化的场景。调频性能约束是联合优化和单独调峰优化最大的区别。调频市场对响应时间和调节精度有要求在模型中体现为调频功率的上限和调频里程的期望值约束。我用的做法是设定一个调频里程系数表征实际调频里程与调频容量之比。这个系数在AGC运行中一般取0.5到1.0之间联合优化模型里把它作为常数输入可以扫描不同系数值看收益变化。这是一个很好的灵敏度分析切入点后面会细说。2. 代码实现与架构解析2.1 MATLAB YALMIP建模技巧整个代码我按“参数初始化—模型定义—求解—结果处理—可视化”五个模块来组织。参数初始化写在最前面这样换一个储能参数或电价数据只需要改最前面的几行配置后面不用动。这是做优化模型代码最重要的工程素养不然每次跑一个新算例都要通读整个代码改参数光是找参数位置就得花半个小时。YALMIP建模部分有几个技巧值得说。第一优化变量用三维还是二维我的做法是调峰充电功率、调峰放电功率、调频功率可以是正负变量、SOC这四个都是96×1的向量不要用矩阵减少不必要的维度YALMIP在处理向量变量时的速度和稳定性都要好一些。第二二进制变量的使用。如果要做“不能同时充放电”这种互斥约束需要引入96个二进制变量YALMIP直接用binvar定义。但如果你的储能在两个市场同时运行时允许充电和调频同时存在放电方向调频那二进制变量就不是必须的了。我做的模型里调峰充电和调峰放电互斥用二进制变量调频功率和调峰放电可以同时存在例如调峰放电同时向下调频相当于少放一点所以调频功率本身可以正负波动。目标函数的写法有个小技巧用repmat把价格向量扩展成矩阵然后一次点乘求和不要写for循环累加。我见过有人用for循环逐时段加目标项几十个变量还行变量到几百个之后求解速度差很多。YALMIP底层是符号运算向量化写法和循环写法的解析开销至少差一个数量级。2.2 求解器配置与性能调优CPLEX的mip参数设置对求解时间影响显著。我这套模型变量规模是96个时段×5个连续变量加96个二进制变量加起来大概576个变量约束大概七八百条。这个规模对CPLEX来说是小意思默认参数几秒就能求到最优解。但如果把调度间隔改成5分钟变量规模变成原来的3倍求解时间可能不是线性增长而是指数增长。这时候就要开一些CPLEX的加速选项比如mip.pool.absgap设置为一个可以接受的小值如0.01允许次优解而不是追求完全最优工程上完全可行。调频里程系数如果作为参数扫灵敏度要做循环求解每次求解前用yalmipt和optimize重新求解。我在代码里单独写了一个灵敏度分析模块循环扫描里程系数0.3到1.0步长0.1每次求解后记录收益指标最后画曲线。跑这个模块的时候建议把CPLEX的输出关掉savesolverlog 0不然几十次求解的日志会刷屏刷到怀疑人生。YALMIP建模还有一个坑变量名冲突。如果你在同一个脚本里多次运行不同规模的模型YALMIP可能会缓存旧的变量定义导致模型和变量对不上结果全错。我的习惯是每次建模前先清除工作区变量或者每次都用不同的变量名后缀区分不同场景。更稳妥的做法是把建模过程封装成函数输入参数输出求解结果函数内部所有变量都是局部的避免污染。2.3 典型日场景与参数设置模型跑通后输入数据决定了结果的价值。我设计了一个典型的工业储能场景储能容量2MW/4MWh额定功率2MW初始SOC设为50%运行SOC范围10%-90%。调峰电价采用某个典型省份的峰谷分时电价峰时段10点到15点、18点到21点电价0.98元/kWh谷时段23点到次日7点电价0.27元/kWh平段电价0.58元/kWh。调频容量价格取50元/MW调频里程价格取8元/MW。这个参数下的直觉结果是谷时段充电峰时段放电同时全天保留一定的功率和SOC余量参与调频。但具体的最优策略是什么只有跑了模型才知道。实际结果确实印证了一些反直觉的结论后面细说。我觉得有必要单独强调一下调频容量和调峰放电不是简单的“非此即彼”关系。因为调频功率是波动的可以在放电基准值上下浮动所以储能可以在高峰时段一边放电调峰一边预留一部分调频容量向下调节少放一点电。这种“放电向下调频”的组合相当于在调峰的同时给电网提供下调频服务收益是叠加的而且因为下调频不用额外充电成本几乎为零。联合优化模型会自动发现这些套利空间这也是联合优化价值的最直接体现。3. 结果分析与策略对比3.1 联合优化 vs 单独优化结果对比我跑了三种模式做对比纯调峰优化、纯调频优化、联合优化。纯调峰的结果不意外SOC呈现典型的低谷充电、高峰放电形态收益主要来自峰谷价差全天SOC变化范围大基本把10%-90%的SOC范围跑满了。纯调频的结果也符合预期SOC基本稳定在初始值附近储能主要输出秒级功率收益主要来自容量租赁和里程结算但因为没有参与峰谷套利收益上限不高且如果调频里程系数较低整体收益甚至可能覆盖不了电池损耗成本。联合优化的结果比前两者有趣得多。它的SOC轨迹不像纯调峰那么“大开大合”高峰放电深度被刻意控制住了比如纯调峰可能放到SOC 20%联合优化只放到SOC 40%。因为SOC越深剩余可用的调频容量越少调频收益损失越大。在调频里程价格足够高的情况下模型会主动放弃一部分调峰收益来保住调频能力。这个行为完全符合理论预期但从结果上看特别直观是我觉得这个模型最有教学价值的一个输出。数值上我设定调频里程系数为0.6时联合优化总收益比纯调峰高约23%比纯调频高约180%。这组数据可能会被一些人直接引用到论文里我要提醒的是这个比例高度依赖调频价格参数。如果调频价格降一半联合优化的优势可能缩小到几个百分点。参数不同结论可能反转这个也值得做灵敏度分析。3.2 关键灵敏度分析调频里程价格是影响联合优化收益分配的最敏感参数。我扫了里程价格从4元/MW到16元/MW的变化当价格低于某个阈值时模型几乎不给调频留容量退化成纯调峰优化当价格高于另一个阈值时模型几乎完全转向调频调峰放电深度被压得非常浅。这个拐点可以用线性规划的对偶理论来解释但在实践中直接扫参就能可视化更重要的是可以给储能电站的投资决策提供参考——如果现货市场的调频价格长期低于这个拐点那么联合优化的价值就主要在调峰侧如果调频价格高那么储能的容量配置和功率配置策略都要重新调整。容量配置灵敏度也很有意思。我固定功率2MW扫描容量从2MWh到8MWh观察总收益变化。结果是容量从2MWh增加到4MWh时收益显著增长从4MWh增加到8MWh时收益增长趋缓。这是因为调峰套利收益受限于一天内的充放电循环次数容量大了之后低频段的收益没有显著提升而调频收益受限于功率而不太受容量影响。这个结果对项目前期容量配置决策有直接参考意义——盲目加大容量不一定带来线性收益增长。3.3 代码复现的难点与心得代码复现过程中最难的其实不是建模而是“把结果调得合理”。我第一版代码跑出来联合优化的结果竟然SOC一直趴在50%附近收益比纯调峰还低明显不合理。排查了很久发现问题是约束条件里调频功率和调峰功率的耦合关系写错了我把调频功率直接加了绝对值导致模型把调频功率限制得非常死不敢参与调峰。修正之后结果就对了。这类问题很隐蔽因为模型不报错MILP也能正常求解但结果就是不对。我的排查经验是盯住SOC轨迹和充放电功率的物理合理性。如果发现SOC轨迹完全不符合直觉或者功率序列出现在某个方向长时间小幅度抖动的振荡模式大概率是约束遗漏或建模错误而不是参数问题。先把约束一条条列出来用小的测试样例验证再跑完整模型效率最高。4. 模型扩展方向与后续改进这一部分讲两个我实际在做、但还没完全跑通的扩展方向给想在这个模型基础上继续做的朋友一个参考。第一个方向是多储能聚合优化。单台储能的功率和容量有限在调频市场上单独投标缺乏竞争力。把多台分布在不同位置的储能聚合成一个虚拟电厂来参与调峰调频需要把每台储能的SOC状态、功率能力、损耗特性统一建模目标函数多了聚合收益分配这个维度。这个问题的难点在于各储能之间的SOC差异和充放电损耗差异很大简单按功率比例分配任务通常不是最优的需要引入考虑能量状态均衡的协同策略。数学上仍然可以写成MILP但变量规模和复杂度会上一个台阶。第二个方向是考虑电价和调频信号的随机性从确定性优化走向随机优化或鲁棒优化。目前这个模型用的是确定性电价和确定性调频里程系数实际运行中电价会波动调频里程也具有很强的随机性。把不确定性建模成场景集用随机规划或分布鲁棒优化来处理会让策略更贴近实际。代价是求解难度急剧上升我目前在测试的是用场景缩减算法把大量历史场景缩减到几十个代表性场景再求解。这个方向理论深度够作为论文的创新点很合适但工程落地还需要时间。5. 常见问题与调试速查我把整个开发过程中遇到的高频问题整理成了一个速查表任何一个跑过类似模型的同行应该都会碰到其中几个。症状可能原因解决方案求解结果显示infeasible约束过紧或初始SOC与约束冲突检查SOC初始值与上下限约束是否矛盾逐条注释约束定位冲突源求解时间过长二进制变量太多或MIP参数未调节开CPLEX的mip.display2观察gap收敛曲线尝试设置mip tolerancesSOC轨迹在调频扰动下越界缺少调频对SOC影响的约束加入基于期望调频里程的SOC偏移约束结果中充放电频繁切换没有限制最小充放电持续时间加入二进制变量和最小持续时间约束如每段至少持续2个时段联合优化结果和单独优化一样调频价格参数设得太低拐点未触发扫描调频价格参数找到模型行为变化的拐点YALMIP报错变量维度不匹配变量定义维度与实际使用不一致用size()检查变量维度统一用96×1向量定义最后一个问题值得单独展开最小持续时间约束。如果没有这个约束MILP在追求目标最优时可能给出“充-放-充-放”交替的锯齿形功率曲线物理上虽然可行但实际储能系统这样运行会严重损害电池寿命而且PCS也受不了。我加了一个约束相邻时段之间的充放电状态切换次数限制或者等效的最小持续时间约束。具体做法是定义充放电状态二进制变量u_ch和u_disch然后加上约束如果上一个时段是充电状态那么当前时段如果要切到放电必须已经有连续K个时段的充电。这个约束本质上是一个状态记忆约束用YALMIP写需要引入额外变量但值得做不然跑出来的“最优解”根本没法在实际系统中执行。实践中如果发现模型结果里出现高频抖动的功率曲线我通常的第一反应不是去调参数而是先挖掘是不是缺少状态平滑约束。把这个问题解决了结果会立刻变得正常很多。6. 最后想说的几个实操细节最后补充几点从项目实践中沉淀下来的经验这些细节在文献里基本不会写但直接决定模型能否从“论文算例”变成“工程可用”的东西。第一点是电价数据的预处理。很多人直接拿原始电价数据进模型结果因为电价曲线里有异常尖峰优化结果变得很不稳定。我的建议是先进滤波或平滑再根据分时特征做分段处理确保优化输入的电价曲线符合市场实际交易规则而不是把噪声也当成套利信号。第二点是调频里程系数的标定。这个系数如果你没有实测数据千万别拍脑袋取0.8。它受电网AGC信号特性影响极大不同区域电网差异可以到3倍。没有实测数据时宁可保守取0.4-0.5也不要取高值。模型结果的收益估算会随这个系数线性变化取高了会让投资决策偏乐观。第三点是模型代码的工程化封装。我建议把模型写成一个核心函数输入参数用struct传输出结果也用struct返回。这样换参数只需要改main函数里的参数结构体字段不用碰模型函数。后续想做敏感性分析、随机模拟或者结果批量导出都会轻松很多。最后从时间投入角度说一句如果你只是想在论文里加一个算例那么照这篇博客的框架复现一轮联合优化的核心流程顺利的话两三天能跑通。但如果你想把这个模型用在实际储能电站的运营决策中那后面还有很长一段路要走——从日前优化到日内滚动优化从确定性模型到随机模型从单机到聚合每一步都是新的优化问题。这个方向上踩过的坑有很多值得单独讲后面有时间再写。