ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

能量与频率服务联合出清的MISOCP建模与价格提取实践

能量与频率服务联合出清的MISOCP建模与价格提取实践 最近在做一个区域电网的能量与频率服务联合出清项目核心就是把混合整数二阶锥规划MISOCP这套东西真正落地到市场清算里。以前很多系统都是能量市场和调频市场分开跑先算能量、再按固定比例去预购调频备用看起来省事但新能源渗透率一上来这种顺次出清的方式问题越来越明显调频需求估不准、备用容量和能量出力抢同一台机组、价格信号互相打架。把能量与频率服务放进同一个优化问题里联合清算再用MISOCP去解是这几年行业内比较主流的做法。这篇文章想写的就是这个项目里我实际踩过的坑、建过的模型、调过的求解器参数以及最后怎么把价格从模型里“抠”出来。内容主要面向电力市场出清算法的工程师、交易中心的技术负责人还有研究优化调度和凸松弛方向的研究生。如果你正准备从顺序出清切到联合出清或者刚接触二阶锥松弛这篇文章基本可以当一份实操笔记来参考。1. 联合出清为什么找上MISOCP1.1 能量和调频抢的是同一度电先说清楚一个基本事实一台机组的容量是有限的它发了能量就不能同时把全部容量都留给调频反之亦然。调频服务本质上不是一种“独立的商品”而是发电容量在后备状态下的机会成本。顺序出清的问题是先出清能量市场剩下多少容量再拿去应付频率需求这种方式在系统稳定、负荷可预测的时代勉强能用但风电光伏多了以后系统惯量下降、频率波动加剧调频需求变成了一条实时变化的曲线再用“固定比例”去预留备用要么过度购买造成浪费要么买少了导致频率越限风险。联合出清的核心思路是把能量和调频容量放在同一个优化问题里让模型自己去权衡“这台机组是发更多的电划算还是留一部分容量去拿调频补偿划算”。这种权衡会产生一个内生价格——调频容量的机会成本。而MISOCP恰好能把这个权衡过程编码进数学规划模型里它不怕有整数变量机组启停也不怕有功无功的二次耦合约束潮流方程所以这个题目本质上不是“要不要用新技术”而是“这类耦合出清问题必须要用这种能同时处理离散变量和非线性凸约束的工具去解”。1.2 交流潮流是个坑凸松弛把坑填平之前做能量市场出清很多人习惯用直流潮流DC Power Flow简化问题因为线性约束加线性目标求解又快又稳。但联合出清里要评估频率服务频率问题恰恰和无功、电压强相关如果完全忽略无功和电压算出来的调频容量可能在实际运行中根本无法兑现。这时候就得回到交流潮流AC Power Flow的框架下但交流潮流方程是非凸的直接放进优化问题里求全局最优几乎不可能。业界这几年大量采用二阶锥松弛SOC Relaxation来“填平”这个坑。简单说把一个非凸的交流潮流可行域放大成一个凸的二阶锥可行域在这个放大的域里求解如果解回代到原非凸方程里误差足够小那我们得到的解就是原问题的最优解。这个技术路线做能源与频率服务联合清算比直接上非线性规划NLP要稳定得多——NLP对初值敏感容易卡在局部最优而二阶锥是凸的优化器能在多项式时间内找到全局最优或者接近最优的界。1.3 整数变量躲不掉的启停约束纯粹的二阶锥规划SOCP只能处理连续变量。但市场出清模型里机组启停是天然的0/1决策变量这台机组到底开不开机开机要花启动成本状态切换有最小开关机时间约束这些都没法用连续变量圆滑地表达。于是SOCP就升级成了MISOCP——在二阶锥约束的基础上叠加0/1整数变量。也正是这一层整数变量让求解难度跳了一个量级。连续SOCP可以用内点法几秒钟算完但加了整数之后求解器要做分支定界Branch and Bound每棵搜索树的节点都要解一次连续SOCP节点数量一上来计算时间从秒级跳到分钟级甚至小时级。接下来要讲的模型设计、参数调优很多动作其实都是在跟这一层整数变量做博弈。2. 模型怎么建以IEEE 14节点为例2.1 目标函数报什么价、怎么进模型我给了一个14节点系统做原型验证但建模思路可以直接迁到实际电网。目标函数很简单最小化总运行成本能量成本加上调频容量成本再加上机组启动成本。能量成本部分是分段线性函数发电机供应商通常报几段递增的阶梯价格调频容量成本是提供商对预留调频容量的报价启动成本是每次开机的一次性开销。用数学语言写出来大概是目标函数min Σ_{g,t} [ C_P(g,t) * P_g,t C_R(g,t) * R_g,t C_startup(g) * y_g,t ]其中y_g,t是“机组在t时段发生了启动动作”的0/1变量。这里有一个很容易犯的错误能量报价曲线如果分段价格不是递增的目标函数就不是凸函数求解器不得不引入额外的二进制变量来做分段选择整个模型变量数量会瞬间爆炸。所以我强烈建议在预处理阶段把能量报价曲线做凸化处理取下凸包或强制分段斜率递增否则后面每一步都会很痛苦。2.2 约束体系里的四层逻辑我把这个模型的核心约束分成四层来梳理。第一层是发电约束每台机组的出力上下限、爬坡约束、最小开关机时间。出力上下限要和启停变量z相乘比如P_min_g * z_g,t ≤ P_g,t ≤ P_max_g * z_g,t第二层是网络约束使用支路潮流模型Branch Flow Model, BFM。每个节点要满足功率平衡每条支路要满足电压降落方程和线路容量约束。线路容量约束是一条标准的旋转锥P_ij,t^2 Q_ij,t^2 ≤ S_max_ij^2第三层是频率服务约束系统调频需求必须被满足所有机组预留的调频容量加总要达到系统需求Σ_g R_g,t ≥ R_need,tR_need可以根据系统最大单机跳闸容量、历史负荷波动和新能源预测误差来设定如果用固定比例则会退化成顺序出清的老路。第四层是耦合约束这是联合出清的核心所在。机组的能量出力P_g,t和调频容量R_g,t不能分别压到各自的极限必须留出共同裕度P_g,t R_g,t ≤ P_max_g * z_g,tP_g,t - R_g,t ≥ P_min_g * z_g,t 爬坡约束也要把调频容量叠加上去(P_g,t - P_g,t-1) R_g,t ≤ Ramp_up_g(P_g,t-1 - P_g,t) R_g,t ≤ Ramp_down_g这四个逻辑里第四层最容易被忽略但恰恰是它让能量市场和调频服务真正“联合”了起来。如果没有这一层耦合约束调频容量就变成了一种虚的、可以凭空产生的商品出清结果在物理上完全不可执行。2.3 二阶锥怎么写进求解器对没怎么接触过二阶锥的人我先给个直观印象。标准二阶锥约束长这样|| x ||_2 ≤ t意思是向量x的二范数不大于标量t。很多看似非线性的约束都可以通过变量替换转成这个形式。比如我们常用的旋转锥约束w^2 x^2 ≤ y * z且y≥0, z≥0它可以等价地写成标准SOC|| [2w; 2x; y-z] ||_2 ≤ yz在交流潮流里支路有功P_ij、无功Q_ij、节点电压平方v_i、支路电流平方l_ij之间满足的关系正好是这种旋转锥形式所以二阶锥就成了描述交流潮流的天然工具。对应到支路潮流模型两条核心约束是电压降落方程v_j,t v_i,t - 2(r_ij * P_ij,t x_ij * Q_ij,t) (r_ij^2 x_ij^2) * l_ij,t旋转锥约束P_ij,t^2 Q_ij,t^2 ≤ v_i,t * l_ij,t只要让求解器去处理这两条约束就能在凸优化的框架下获得一个足够精确的交流潮流近似解这就是整篇项目里“二阶锥”三个字的真正分量。3. 实操流程从建模到出价格的一整套动作3.1 数据准备和参数结构我用的IEEE 14节点系统14条母线、20条支路、5台发电机。为了让模型更接近实际我做了几件事。第一把时间粒度切成15分钟一个时段一共跑96个时段这样可以体现出负荷波动和调频需求变化。第二把系统调频需求设为“系统最大单机容量”的50%再加上负荷预测误差项而不是固定负荷比例。第三机组数据里除了Pmin/Pmax、爬坡率、启动成本还额外给了调频容量上限和调频报价这样模型才能真正在能量和调频之间做权衡。给我最直观感受的是数据质量对MISOCP求解时间的影响比模型公式还要大。发电成本和调频报价如果量纲不统一比如一个用元/MWh一个用分/MW或者数值尺度差两三个数量级求解器的数值稳定性会迅速恶化。我的做法是全部转换到标幺值体系基准容量取100MVA成本和价格统一换算成元/MWh电压幅值约在0.9到1.1pu之间这样所有变量和约束数值都在0.01到100的量级范围内求解器跑起来会舒服很多。3.2 核心代码框架我用的是MATLAB YALMIP Gurobi的组合这套组合在电力系统研究和项目原型里非常常见。下面给一段核心约束构建的代码省去数据文件的读取和参数定义重点看二阶锥和耦合约束是怎么写进模型的。%% 决策变量 P_g sdpvar(nG, T, full); % 机组有功出力 R_g sdpvar(nG, T, full); % 机组调频容量 z binvar(nG, T, full); % 机组启停状态 y_st binvar(nG, T, full); % 机组启动动作 v_i sdpvar(nB, T, full); % 节点电压幅值平方 l_ij sdpvar(nL, T, full); % 支路电流幅值平方 P_f sdpvar(nL, T, full); % 支路有功 Q_f sdpvar(nL, T, full); % 支路无功 %% 目标函数 Objective 0; for t 1:T Objective Objective cP * P_g(:,t) cR * R_g(:,t); Objective Objective cStart * y_st(:,t); end %% 约束集合 C []; %% 1. 机组出力与启停约束 C [C, Pmin .* z P_g Pmax .* z]; C [C, Rmin .* z R_g Rmax .* z]; %% 2. 能量-调频耦合约束联合出清的关键 C [C, P_g R_g Pmax .* z]; C [C, P_g - R_g Pmin .* z]; %% 3. 爬坡约束含调频容量影响 C [C, diff(P_g, 1, 2) R_g(:, 2:end) RampUp]; C [C, -diff(P_g, 1, 2) R_g(:, 2:end) RampDown]; %% 4. 系统调频需求约束 C [C, sum(R_g, 1) R_need]; %% 5. 节点功率平衡 for t 1:T for k 1:nB C [C, sum(P_g(genBus k, t)) - P_demand(k, t) ... sum(P_f(outLines{k}, t)) - sum(P_f(inLines{k}, t))]; end end %% 6. 支路潮流与二阶锥约束 for t 1:T for l 1:nL k fromBus(l); j toBus(l); % 电压降落方程 C [C, v_i(j,t) v_i(k,t) ... - 2*(R(l)*P_f(l,t) X(l)*Q_f(l,t)) ... (R(l)^2 X(l)^2) * l_ij(l,t)]; % 旋转锥约束P^2 Q^2 v_i * l_ij C [C, cone([2*P_f(l,t); 2*Q_f(l,t); v_i(k,t)-l_ij(l,t)], ... v_i(k,t) l_ij(l,t))]; % 支路视在功率上限 C [C, cone([P_f(l,t); Q_f(l,t)], S_max(l))]; end end %% 求解 ops sdpsettings(solver, gurobi, verbose, 2, ... showprogress, 1, debug, 1); optimize(C, Objective, ops);这里用YALMIP的cone(x, t)表示||x||₂≤t旋转锥约束的标准化写法我之前说过代码里直接套用即可。这段代码跑通之后你会在输出里看到最优目标值但更值钱的是从模型里提取影子价格。3.3 市场出清价格怎么提出来这是整个联合清算项目里最重要的一个技术动作。先说结论MISOCP本身的对偶变量不能直接用作市场价格因为整数变量的存在让MIP问题没有标准意义上的KKT对偶。我见过不少团队在这里栽跟头——直接去读dual()结果报出来的价格忽高忽低交易规则根本没法用。正确的操作分两步。第一步用Gurobi求出10进制最优的整数解把机组启停变量z_g,t固定下来。第二步把z代入原模型此时剩下的问题是一个纯连续变量下的SOCP问题也就是凸问题存在唯一的对偶解这时候再去提取价格就合法了。在YALMIP里的实现很直接z_fixed value(z); C_fixed replace(C, z, z_fixed); optimize(C_fixed, Objective, ops); % 提取节点能量边际价格节点电价 LMP dual(C_fixed(ismember(C_fixed, energy_balance_constraints))); % 提取系统调频容量价格 frequency_price dual(C_fixed(ismember(C_fixed, reserve_requirement_constraints)));拉格朗日乘子的含义要解释一下能量平衡约束的对偶变量就是该节点增加单位负荷时系统总成本的变化量也就是节点边际电价系统调频需求约束的对偶变量是系统多增加1MW调频需求时总成本的变化量也就是调频容量的系统边际价格。因为频率是全局物理量所以我算出来的是一个统一价格而不是分节点价格。实测下来14节点系统在96时段下能量价格曲线有明显的早晚高峰调频价格也在负荷快速爬坡时段被抬高——这个信号是正确的它反映了调频容量的稀缺程度。3.4 求解时间与数值稳定性调优我刚跑通模型的第一个版本其实很慢。96时段、5台机组、20条支路模型里大约有300多个0/1变量加上几千条SOC约束Gurobi默认参数跑了将近40分钟才收敛到3%的gap。对这个量级的市场清算来说分钟级还能接受但40分钟显然不可用。我的调优动作主要有三个。第一给Gurobi设置合理的MIPGap和TimeLimit比如MIPGap1e-3配合TimeLimit300s这样它能先快速找到一个可行的次优解再慢慢证明最优性实际市场清算完全不需要证明到绝对的0.01%以下。第二开启热启动用前一天的历史启停计划作为MIPStart输入能把初始界大幅收紧。第三检查大M系数是否有被放大过的合理值——很多电力系统的模型喜欢把大M设成1e6这是一个非常坏的习惯最优做法是结合机组容量和线路极限把大M压到实际物理极限的1.1倍左右。数值稳定性的另一个重点是电压幅值的上下界。我一开始给的是0.9到1.1pu后来发现二阶锥松弛的紧性严重依赖这个界界越宽、松弛空隙越大。如果实际运行中电压都在0.95到1.05附近就把这个区间收紧SOC gap会迅速减小求解时间也能降下来。4. 常见问题与排查技巧实录4.1 二阶锥松弛不紧怎么办二阶锥松弛本质上是把等式约束放松成了不等式所以解出来之后必须检查松弛是不是“紧”的——如果紧说明解在原始可行域上如果不紧这个解实际对应了一个物理上不成立的潮流。检查方法很简单把每个时段、每条支路的SOC gap算出来gap_ij,t v_i(k,t) * l_ij(l,t) - P_f(l,t)^2 - Q_f(l,t)^2如果这个值超过1e-4量级就要小心了。我踩过的坑里最常导致松弛不紧的是电压下界给得过低。很多文献喜欢给0.8pu甚至更低但实际系统在正常情况下根本不会跑到那么低给低只会让锥体“膨胀”。把电压下界从0.8收回到0.95之后gap立刻降了两个数量级。如果保守调界还是不行再考虑加0-1变量的线性割平面来收紧可行域但这个优先级靠后先查电压界和负荷数据。4.2 MISOCP求解太慢如果你的整数变量规模到了几千甚至上万默认求解器基本会卡在分支定界树上。我有几个实操建议。一是压缩时间粒度比如96时段如果算不完先跑24时段验证逻辑二是把启动变量y和状态变量z做线性约束关联避免求解器在无意义的组合上浪费时间三是用“先连续、后整数”的策略先忽略整数再跑一次连续SOCP然后把连续解里的启停状态离散化作为初始解喂回去。Gurobi有几个隐藏参数对MISOCP很有效最典型的组合是MIPFocus2加NumericFocus1前者让求解器偏向推进下界后者提升数值处理稳定度。如果模型本身非常病态可以考虑换MOSEK去解固定整数之后的连续SOCP它在连续二阶锥上的内点法比Gurobi更快更稳整数和连续问题分开用不同求解器也是项目里常见的工程方案。4.3 出清价格出现负值或抖动价格出现负值不一定是模型错了新能源占比高的时段有些机组愿意倒贴钱发电负电价是有物理含义的。让人头疼的是价格抖动——相邻时段一个价格高一个价格低交易中心完全没法用。这种情况大部分出在报价曲线的凸化预处理上分段线性报价的拐点如果在最优解附近影子价格会对报价斜率非常敏感微小的负荷变化就会让最优解跳到另一个分段价格随之跳变。我的对策是给能量报价的分段函数加一层限幅平滑或者把报价段数减少——实测下来3到5段就足够表达市场行为段数再多只会加剧数字抖动。实在不行可以在目标函数里加一个很小的正则项比如千分之一的出力二次项能有效平滑价格而不改变市场总成本量级。4.4 调频容量总被压到下限这个现象在初期模型里经常出现。先检查系统调频需求约束是不是被激活的——如果激活了但价格低说明购买调频容量的成本低不是模型问题如果需求约束压根没激活说明R_need定低了系统的真实频率需求没被体现出来。另一个常见原因是耦合约束写得太保守。我在1.1节里把PR≤Pmax写成线性约束但如果机组实际响应调频信号是有延迟的那种瞬时同涨同跌的约束确实可以适当放宽让机组在15分钟时段内先发能量、后调整出力去响应频率就能释放更多有效容量。调频容量压到下限还有一种可能是爬坡约束和调频约束相互挤兑机组刚经历负荷爬坡剩余爬坡能力不足不得不牺牲调频容量这时要区分是“物理上确实没有余量”还是“建模时爬坡率参数过于保守”。下面把以上四类问题整理成一个速查表方便你直接对照排查现象可能原因排查方法快速修复SOC gap过大电压界过宽、负荷数据异常打印每条支路gap值收紧电压上下界到0.95~1.05求解时间过长整数变量过多、初始解差观察BB节点数设置MIPGapMIPStart热启动价格抖动报价曲线分段多且斜率陡对比相邻时段的价格和机组组合减少报价段数、加平滑或正则项调频容量被压到下限R_need偏低或爬坡约束过紧检查需求约束对偶变量是否激活重新核算R_need、放宽爬坡率参数回到项目本身我最大的体会是MISOCP这套东西优化建模其实是整个项目里最不费劲的部分真正花时间的是对物理约束的理解和价格信号的解读。联合出清看上去是数学问题本质上是系统运行经济性与物理可行性的平衡问题。最后分享一个做原型验证的小技巧第一次搭建这类模型时先忽略网络约束只跑单节点的能量-调频联合出清把逻辑跑通后再逐步加上网架这样能把模型问题、代码问题、数据问题分开排查比一次性上全模型要省一半调试时间。
返回列表