news 2026/9/10 5:44:06

旋转备用联合出清模型:原理、Matlab实现与出清价格分析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
旋转备用联合出清模型:原理、Matlab实现与出清价格分析

1. 为什么要单独研究旋转备用出清

1.1 旋转备用在电力市场里的角色

做电力市场出清模型的朋友应该都有体会,能量市场大家做得很多,但真正把辅助服务一起纳入联合优化的时候,问题就开始复杂了。旋转备用(Spinning Reserve)是所有辅助服务品类里最特殊的一个,它要求机组处于同步运行状态,能够随时响应调度指令,在规定时间内完成出力调整。也就是说,不是所有机组都有资格提供旋转备用,你停机了就是停机了,冷备用、热备用都无法替代旋转备用的实时响应能力。

那为什么要把旋转备用单独拿出来做市场出清模型?核心原因在于,旋转备用的成本结构和能量不同。能量市场的报价反映的是发电变动成本,而旋转备用一旦被调用,机组就失去了在能量市场出售这部分电量的机会,这个损失叫作机会成本。更麻烦的是,旋转备用的容量占用和能量出清互相耦合——一台机组如果把自己的容量全部报给能量市场,它就没有办法再提供备用;反过来,预留了备用容量,就压缩了它在能量市场的竞价空间。所以主辅助服务市场出清绝不是一个简单的“能量一个场、备用一个场”的独立问题,而是需要在一个统一框架下联合优化。

我接下来说的这套Matlab代码实现,解决的就是这个联合出清问题。它把能量市场出清与旋转备用市场出清合并进同一个优化模型,用线性规划求解,最终同时得到机组出力计划、备用容量分配、能量出清价和备用容量出清价。这套模型在学术研究中很常见,也是目前国内电力现货市场“电能量+辅助服务”联合出清的基本思路之一,适合电力市场方向的研究生、售电公司分析人员以及电网调度侧做市场规则验证的工程师参考。

1.2 联合出清与顺序出清的取舍

先聊一个每个做辅助服务出清的人都会面对的问题:到底是顺序出清还是联合出清?

顺序出清很简单,先跑能量市场,跑完拿到机组出力和边际电价,再根据结果安排备用容量。这种方式的优点在于规则清晰、计算拆分,缺点是能量市场出清结果未必给备用留出足够的容量空间。极端情况下可能出现能量市场把某台大机组的容量全部用满,到了备用市场发现系统旋转备用不足,再回头强行压低能量市场出清结果,造成二次调整和不公平的结算。

联合出清则把能量和备用放进同一个目标函数里,让优化器来自动权衡“发一度电”和“留一兆瓦备用”哪个更有价值。从数学本质上讲,联合出清是在满足负荷平衡、备用容量需求、机组出力上下限等全部约束的前提下,求总购电成本最小化。这样处理的好处是,备用容量占用不会凭空消失,它在目标函数里天然抬高了机组的综合边际成本,出清价格能够更真实地反映系统资源稀缺性。

我个人的观点是,研究阶段优先做联合出清模型,它更贴近“市场出清”这个词的本质。等联合出清的代码跑通了,再去对比顺序出清的结果,你会非常直观地看到两种机制对机组备用分配和价格的影响差异。这也是很多论文的标准做法。

2. 出清模型的数学表达

2.1 目标函数设计思路

旋转备用出清模型的目标函数,我在项目里用的是这样一个形式:

在实际建模中,我假设所有机组在出清时刻都处于运行状态,因此目标函数只包含两部分:能量费用和备用容量费用。能量费用用机组的能量报价曲线(通常近似为分段线性函数)乘以出力计算;备用容量费用用备用报价乘以备用容量计算。

目标函数可以表达为:

[ \min \sum_{i=1}^{N} C_i(P_i) + \sum_{i=1}^{N} R_i \cdot RC_i ]

其中:

  • (P_i) 是机组 i 的有功出力
  • (C_i(P_i)) 是机组 i 的能量成本函数,简化为线性函数 (C_i(P_i) = a_i P_i + b_i u_i)
  • (R_i) 是机组 i 提供的旋转备用容量
  • (RC_i) 是机组 i 的备用容量报价

为什么用线性函数而不是二次函数?这是我在实际项目中踩过的一个选择点。线性规划求解效率高、收敛稳定,出清价格信息提取非常方便——对偶乘子就是价格。二次规划虽然能更精细地反映机组煤耗曲线,但求解速度和数值稳定性都要差一些,而且对于市场出清模型,分段线性报价更接近真实市场规则。国内电力现货市场目前采用的分段报价机制,本质上就是线性化处理。

2.2 约束条件逐条拆解

模型的核心约束,我在代码里写全了这样几条:

第一条是系统功率平衡约束:

[ \sum_{i=1}^{N} P_i = D ]

D 是系统总负荷。这个约束的拉格朗日乘子,就是系统能量的边际价格——也就是我们常说的系统能量出清价(SMP,System Marginal Price)。很多做市场分析的朋友问我出清价到底怎么从模型里拿,答案就在这条约束的对偶变量里。

第二条是旋转备用容量需求约束:

[ \sum_{i=1}^{N} R_i \geq R_{req} ]

(R_{req}) 是系统所需旋转备用容量。旋转备用需求怎么定?工程实践里比较通用的做法是按“最大单机容量”设定,也就是系统需要保证任何一台机组跳闸后,剩余旋转备用仍然能满足负荷需求。也有的规则按系统峰值负荷的某个百分比(比如5%~10%)设定。在代码里我把 (R_{req}) 设成参数,方便不同场景切换。

第三条是机组出力与备用容量耦合约束:

[ P_i + R_i \leq P_{i,\max} ]

[ P_i \geq P_{i,\min} ]

[ 0 \leq R_i \leq R_{i,\max} ]

这里最关键的是 (P_i + R_i \leq P_{i,\max})。它表达的是物理事实:一台机组的出力加上它预留的备用容量,不能超过机组的最大技术出力。这个约束直接体现了“能量和备用竞争同一块容量”的本质,也是联合出清和顺序出清结果差异的主要来源。

第四条是旋转备用响应能力约束:

[ R_i \leq r_{i,10} \cdot 10 ]

这表示机组提供的旋转备用量必须在10分钟内能够完全响应(即满足10分钟爬坡速率约束)。如果机组爬坡速率很快,这个约束自然松弛;如果机组爬坡慢,即使有富余容量也不能提供同样多的备用。这是旋转备用区别于其他备用品种的重要约束,很多初学做模型的人容易漏掉。

2.3 旋转备用价格怎么算

能量出清价的提取上面说了,取功率平衡约束的对偶乘子。备用容量出清价格则要取备用容量需求约束的对偶乘子。

值得特别注意的是,当备用容量需求约束紧(即 (\sum R_i = R_{req}))时,备用价格等于边际机组提供一单位备用的边际成本;而当备用容量充裕、约束松弛时,备用出清价格会趋向于零。这不是模型bug,而是市场实际的反映——备用不缺的时候,额外的备用容量不会产生价值。

另外还有一个容易被忽略的细节,就是能量价格与备用价格之间的联动关系。为什么备用价格很少等于备用报价?因为那些同时处于能量出清边际的机组,多提供一单位备用就意味着少发一单位电,这部分能量收益损失必须通过备用价格补偿。这就是为什么很多文献强调,旋转备用的完整成本应该包含“运行成本增量 + 机会成本”两个部分。我建议在代码求完价格后,把每台机组的机会成本单独算出来,你会看到它和备用出清价格之间高度相关。

3. Matlab代码实现落地

3.1 建模工具选型

Matlab做优化建模,我在这个项目里用的是Yalmip+gurobi的组合方案。纯新手可能对Yalmip比较陌生,我稍微解释一下:Yalmip是Matlab下一个免费的开源建模工具,它把线性规划、整数规划、二次规划等不同类型的问题统一成一套建模语法,底层再调用不同的求解器,比如gurobi、cplex、mosek等。

用Yalmip有几个好处。第一是建模速度快,它允许直接用符号变量定义优化变量,写约束的方式和数学表达式几乎一一对应,调试起来非常方便。第二是换求解器成本低,同一套代码,在gurobi和cplex之间切换只需要改一行设置。第三是拿对偶乘子方便,yalmiptime里提供了dual(constraint)接口,直接提取约束对应的拉格朗日乘子,不需要手动翻译KKT条件。

不过有一点我要提醒,Yalmip安装后一定要自己装一个求解器,它本身不包含求解功能。如果用的是sedumilinprog也能跑小算例,但速度太慢,商用问题建议还是gurobi或cplex,有学术license免费申请。

3.2 核心代码框架

下面这段是模型的核心实现,我省略了数据读取和结果绘图部分,保留最关键的建模段。代码里机组数量、负荷数据、备用需求这些都是按矩阵和向量组织的,方便扩展到更大规模的系统。

%% 旋转备用联合出清模型 % 决策变量: P_i 出力, R_i 备用容量 % 目标: 能量成本 + 备用容量费用最小化 % 求解器: gurobi via yalmip % 基础数据(3机系统示例) Pmax = [100; 150; 200]; % 最大出力 MW Pmin = [20; 30; 40]; % 最小出力 MW a = [0.20; 0.25; 0.18]; % 能量报价 元/MWh b = [50; 60; 55]; % 空载成本 元/h RC = [30; 35; 28]; % 备用容量报价 元/MWh Rmax = [50; 60; 70]; % 最大可提供旋转备用 MW ru10 = [6; 8; 10]; % 10分钟爬坡速率 MW/min D = 300; % 系统总负荷 MW Rreq = 100; % 旋转备用需求 MW % 定义优化变量 P = sdpvar(3, 1); R = sdpvar(3, 1); % 目标函数 Objective = a'*P + b'*ones(3,1) + RC'*R; % 约束 Constraints = []; % 功率平衡 Constraints = [Constraints, sum(P) == D]; % 旋转备用需求 Constraints = [Constraints, sum(R) >= Rreq]; % 机组出力上下限 Constraints = [Constraints, P >= Pmin]; Constraints = [Constraints, P <= Pmax]; % 出力和备用耦合约束 Constraints = [Constraints, P + R <= Pmax]; % 备用上下限和爬坡约束 Constraints = [Constraints, R >= 0]; Constraints = [Constraints, R <= Rmax]; Constraints = [Constraints, R <= ru10 * 10]; % 求解 ops = sdpsettings('solver', 'gurobi', 'verbose', 0); optimize(Constraints, Objective, ops); % 提取结果 P_result = value(P); R_result = value(R); energy_price = dual(Constraints(1)); % 功率平衡约束对偶乘子 reserve_price = dual(Constraints(2)); % 备用需求约束对偶乘子 fprintf('能量出清价: %.2f 元/MWh\n', energy_price); fprintf('备用容量出清价: %.2f 元/MWh\n', reserve_price); fprintf('各机组出力: '); disp(P_result'); fprintf('各机组备用: '); disp(R_result');

这段代码虽然短,但已经把上面数学模型里的每一个约束都实现了。我特别说一下约束矩阵的索引问题:在Matlab代码里我用Constraints(1)指向功率平衡约束,用Constraints(2)指向备用需求约束。这里有个坑——如果你在约束定义过程中临时改变了约束声明顺序,索引就会对不上。所以我一般建议在约束写完之后,用assign或直接打印Constraints来核对顺序,不然拿到的对偶乘子可能张冠李戴。

3.3 算例与参数设置

我测试用的是3机系统,目的就是快速验证模型逻辑,数据设计也有讲究:

  • 1号机组:最大100MW,能量报价0.20元/MWh,备用报价30元/MWh,属于典型高成本小机组
  • 2号机组:最大150MW,能量报价0.25元/MWh,备用报价35元/MWh,典型中间层
  • 3号机组:最大200MW,能量报价0.18元/MWh,备用报价28元/MWh,明显是经济型大机组

负荷D取300MW,备用需求Rreq取100MW。这个备用需求不是拍脑袋设的,按N-1准则近似,当最大机组200MW时取50%作为需求,也可以认为系统要求提供最大机组一半容量的旋转备用;实际调度中取100MW比较符合中小型测试系统的常见设定。

运行代码后会看到,3号机组由于能量报价最低,会优先加载出力;1号和2号机组再根据各自的备用报价情况承担备用任务。具体的出力分配要看优化结果,因为备用容量报价在目标函数里直接和能量报价竞争,不同报价组合会得到完全不同的分配结果。

建议读者拿到代码后,试着改三组场景:

  1. 把备用需求从100MW提高到180MW,观察出力计划和备用计划的重新分配
  2. 把1号机组的备用报价从30降到10,观察1号机组是否会从“纯出力”变成“大量备用”
  3. 把负荷提高到400MW,此时系统资源变得紧张,观察能量出清价和备用价格的变化幅度

实测下来,第2种场景最能体现联合出清的耦合效应:报价调整后,1号机组的出力可能会下降,把容量让给备用,而3号机组则补上出力的缺口——这就是容量在能量和备用之间再分配的过程。

4. 仿真结果分析

4.1 看什么指标

模型跑完,很多人拿到结果不知道先看什么。我通常按这个顺序来分析:

第一步看出力计划是否合理。每台机组的出力是否在上下限内,是否出现了某台机组出力非常低却承担大量备用的“割裂”情况。如果出现这种情况,往往不是模型错了,而是备用报价太低,优化器发现让这台机组多发备用、少发电更加经济,此时要额外检查它申报的备用容量是否匹配机组的实际爬坡能力。

第二步看备用分配结果。备用容量应该优先分配给备用报价低、且爬坡速率满足要求的机组。如果结果里出现了备用报价高但被大量调用、备用报价低反而不调用的情况,大概率是约束设置有问题,重点检查耦合约束 (P_i + R_i \leq P_{i,\max}) 是否写成了 (P_i \leq P_{i,\max}) 和 (R_i \leq R_{i,\max}) 两条独立约束。

第三步也是最关键的一步,看两个出清价格。能量出清价反映的是系统边际能量成本,备用容量出清价反映的是系统边际备用成本。可以对比这两个价格和机组报价的关系:正常情况下,出清价会落在一个合理区间内,不会低于所有报价,也不会远高于报价上限。如果你发现备用价格波动非常剧烈,试试看备用需求约束是不是处于“临界紧”状态——即约束刚好等于需求,任何微小参数变化都会导致对偶乘子跳变。

第四步,有条件的话做灵敏度分析。我项目里会固定负荷不变,从80MW到150MW循环改变备用需求,画出备用价格和能量价格的曲线。这条曲线能非常直观地告诉你,系统备用资源是在哪个节点开始变稀缺的。价格拐点对应的备用需求值,就是系统备用容量的价值边界,也是市场规则设计者关心的关键参数。

4.2 结果合理性校验

模型结果合不合理,除了看数值范围,还要做一套基础校验。我在代码里加了一个自检函数,包括:

  • 功率平衡校验:(|\sum P_i - D| < epsilon)
  • 备用需求校验:(\sum R_i \geq R_{req})
  • 机组限幅校验:(P_{i,\min} \leq P_i \leq P_{i,\max})
  • 容量耦合校验:(P_i + R_i \leq P_{i,\max})
  • 爬坡校验:(R_i \leq ru_{i,10} \cdot 10)

这些校验如果有一项不满足,就要回头检查约束定义。特别提醒一点,Yalmip给的是数值解,浮点误差是存在的,所以校验时不要用严格等号,要设置一个1e-6级别的容忍度。

另外,价格合理性需要结合经济学的直觉来检查。如果能量出清价明明很高,备用价格却非常低,说明备用约束很不紧,备用资源充裕;反之,如果备用价格高于多台机组的备用报价上限,说明备用需求设置可能超出了系统物理能力,建议下调需求或放开部分机组的备用上限。

5. 常见问题与排查实录

5.1 Yalmip建模报错

我见过的初学者错误里,最高频的是优化变量维度和数据矩阵维度不匹配。比如sdpvar(3,1)定义的是列向量,但后面用行向量加载数据,某些约束运算会直接报维度错误。这个问题解决起来最简单——在定义变量和数据后,在求解前加一行size(P)size(Pmax),肉眼核对维度。

第二个高频问题是约束列表拼接方式错误。正确拼接方式是:

Constraints = []; Constraints = [Constraints, sum(P) == D]; Constraints = [Constraints, P >= Pmin];

注意这里用的是英文逗号或者空格,不是分号。如果用了分号,Constraints会变成一个矩阵而不是一个约束列表,后续optimize函数无法正常解析。这个问题报错信息不太直观,往往是“Index exceeds array bounds”这类让人摸不着头脑的提示,我一开始也被坑过。

5.2 模型不可行怎么排查

模型不可行(infeasible problem)是出清模型里另一个高频问题。根源通常是约束之间互相矛盾。

最常见的情况是负荷和备用需求加起来超过了系统总容量。比如本算例中总最大出力是450MW,如果你设负荷350MW加上备用需求150MW,合计500MW,模型必然无解(除非允许机组全部满载且备用为0,但那样备用约束又不满足)。

解决办法是把模型改成可诊断的格式。Yalmip里有个非常实用的工具,叫optimize的第三个返回值:

diagnostics = optimize(Constraints, Objective, ops); if diagnostics.problem ~= 0 disp('问题诊断:'); disp(diagnostics.info); end

diagnostics.info会给出求解器返回的具体问题类型。如果是不可行,可以用gurobi的 IIS(Irreducible Infeasible Subsystem)功能,我自己的检查方法是逐步注释掉约束,跑一次模型,看看注释掉哪个约束之后模型变得可行——这个约束十有八九就是矛盾源。

5.3 数值问题与求解器参数

第三个故障点是数值稳定性问题。当机组的成本系数数量级差异很大时(比如一个0.01,一个5000),gurobi求解过程中可能出现数值警告甚至错误结果。我在这个模型里就遇到过,备用报价从20元到500元不等,能量报价从0.15元到0.9元,量级差异还不算特别大,但扩展到几百台机组时很容易出问题。

两个应对方法。一是数据归一化,把成本系数统一到同一个数量级;二是在sdpsettings里开启更好的数值处理选项:

ops = sdpsettings('solver', 'gurobi', 'gurobi.NumericFocus', 2, 'gurobi.ScaleFlag', 2);

另外Yalmip默认使用的求解容差是1e-6,如果模型规模较大,可以放宽到1e-4或1e-3,能有效减少无意义的微小迭代。我实测中,小模型用1e-6没问题,但扩展到100台机组以上,容差太紧可能让求解时间成倍增加。

5.4 对偶乘子提取失败

Yalmip里提取对偶乘子需要特别小心一点:必须保证优化问题存在唯一最优解,并且约束引用正确。

如果提取出来是NaN,多半是问题求解失败或者是非凸问题。旋转备用出清模型我按线性规划写,理论上一定收敛,但如果数据里有非线性表达式(比如把成本写成二次函数、又把备用约束写成非线性),Yalmip可能会调用非线性求解器,此时对偶乘子就不一定可靠了。

建议所有对偶分析都基于线性模型完成。如果确实需要二次成本函数,优先考虑用分段线性逼近,而不是直接求解二次规划——前者在出清价提取上更规范。

6. 模型扩展方向

这套基础模型跑通之后,有两条扩展路径比较推荐。一条是引入机组启停状态(即把0-1整数变量加进去,变成混合整数规划MILP),这样可以研究“启停备用”与“旋转备用”的分层协调问题。另一条是引入网络安全约束(直流潮流约束),把模型从单节点扩展为多节点网络,这时候备用出清价就会带上位置信号,形成分区域的备用价格。

我个人在实际操作中更推荐先做第二条扩展,因为电力市场的空间维度是躲不开的。单节点模型虽然学术上方便,但现实中“本地备用”和“全网备用”的价值差异非常大,如果忽略线路传输约束,容易低估局部尖峰负荷地区的备用稀缺性。加入直流潮流约束的思路也很直接,就是把每一台机组和每一个负荷映射到节点上,再增加线路传输容量约束。Yalmip对这类多节点直流潮流建模支持得很好,你只需要提前把节点导纳矩阵和线路功率传输分布因子矩阵算好,剩下的就是拼接约束。

做扩展时还有个体会:不要一上来就把模型做得很复杂。先把单节点线性模型的结果吃透,画出出力分配图和价格曲线,理解了每个参数对结果的影响,再逐步增加复杂度。否则模型出了问题,你可能都不知道是物理约束错了还是网络数据错了。这个经验适用于所有做优化建模的朋友。

以后如果要接论文或者项目结题,可以在模型基础上补充时序联动的细节,比如96点时段的滚动出清,以及备用市场与调频市场的联合出清。核心建模逻辑都一样,只是约束数量和变量维度变大,代码框架不需要推倒重来。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 5:44:00

元青花凭什么贵?稀缺性、艺术价值与鉴定实操全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/10 5:43:58

WSL 如何收集 WSL 侧 ETL 跟踪日志并用 WPA 分析 Windows 进程行为?

WSL 如何收集 WSL 侧 ETL 跟踪日志并用 WPA 分析 Windows 进程行为&#xff1f; 【免费下载链接】WSL Windows Subsystem for Linux 项目地址: https://gitcode.com/GitHub_Trending/ws/WSL 排查 WSL 问题时&#xff0c;debugging.md 给出的第一个日志来源是 ETL trace&…

作者头像 李华
网站建设 2026/9/10 5:40:52

SpringBoot3+SpringSecurity6前后端分离JWT权限认证实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/10 5:39:16

从零搭建智能学习平台:SpringBoot+Vue全栈开发实战与踩坑总结

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/10 5:37:28

智能体系统架构:隔离、集成与治理的范式重构

1. 为什么“智能体系统”不能照搬微服务那一套&#xff1f;“智能体系统架构&#xff1a;隔离、集成与治理的综合调研”——这个标题乍看像一篇学术综述&#xff0c;但如果你真在一线做过大模型应用落地&#xff0c;就会发现它戳中了当前最棘手的工程现实&#xff1a;我们正用微…

作者头像 李华