资讯动态

节点边际电价出清优化:KKT对偶与影子价格实战解析

发布时间:2026/10/3 18:16:07 来源:尧图企业网站定制
简介面向电力市场出清与电价分析研究者的节点边际电价出清优化资源包复现自《机组运行约束对机组节点边际电价的影响分析_史新红》基于YALMIP和CPLEX求解。模型为单时段、未计及爬坡约束其余机组运行约束均纳入通过KKT对偶条件将拉格朗日乘子以影子价格形式提取并整理为矩阵表达是分析节点边际电价的规范实现。程序包含模型构建、对偶求解与结果输出等核心脚本注释清晰配合完整报告与原文文献便于理解系统边际电价与节点边际电价的形成机制。压缩包共5个文件以MATLAB程序.m、报告文档.doc及原文文献.caj为主整体仅383KB轻量易用其中报告可对照代码梳理推导过程适合电力市场初学者、研究生及需要复现节点电价计算的工程师参考使用。目前已有4667人学习下载口碑经过验证。1. 节点边际电价出清优化为什么你算出来的LMP总和系统边际电价对不上做电力市场大作业或者研究节点电价的人大概率有过这种经历用yalmip配cplex把出清模型跑通了目标函数值也对但一到节点边际电价LMP就懵——全网所有节点电价居然都等于同一台机组的报价这和教材里画的“阻塞导致电价分叉”完全对不上。问题通常出在没把机组运行约束的影子价格算进去。这份资源复现了史新红《机组运行约束对机组节点边际电价的影响分析》用KKT对偶把拉格朗日乘子显式求出来并写成矩阵形式。单时段、不考虑爬坡其余约束功率平衡、线路潮流、机组上下限全部覆盖。适合电力市场课程设计、研究生入门以及想验证自己对偶推导对不对的从业者。2. 从LMP到KKT对偶这份资源为什么选“手动对偶矩阵化”2.1 节点边际电价不是“最贵机组的报价”先明确概念。LMP的定义是在满足系统安全约束的前提下为满足某一节点单位负荷增量而增加的系统总成本。数学上它是目标函数对负荷的偏导数也就是节点功率平衡约束对应的拉格朗日乘子即影子价格。好多人刚接触时把它理解成“最后一个被调度机组的报价”这在无阻塞、无机组约束的场景下碰巧成立一旦线路阻塞或者某台机组达到出力上限这个直觉就彻底失效。如果系统没有线路阻塞所有节点LMP相等等于系统边际电价SMP。一旦某条线路达到传输极限这条线路的影响就会通过KKT条件传导到各个节点的功率平衡乘子上LMP随之分叉。同样的模型有人算出来全网一个价有人算出来每个节点不同差别不在目标函数和约束本身而在乘子提取这一步。这份资源里pri.m对应原始问题求解得到的是机组出力和相角dual.m才是关键它把KKT条件逐条写成矩阵方程直接解出每个约束的拉格朗日乘子。对比这两个文件能直观看到机组达到上/下限时约束乘子怎么改变LMP。2.2 拉格朗日乘子怎么变成影子价格考虑标准的直流潮流出清模型min Σ c_i·pg_is.t. B·θ Cg·pg - Pd节点功率平衡乘子λ -Fmax ≤ Bf·θ ≤ Fmax线路潮流乘子μ pg_min ≤ pg ≤ pg_max机组出力上下限乘子ν写成拉格朗日函数等式约束和不等式约束分别引入乘子L Σ c_i·pg_i λᵀ(Bθ - Cg·pg Pd) μᵀ(Bfθ - Fmax) νᵀ(pg - pg_max)在最优解处L对θ和pg的偏导必为0。对θ求导得到Bᵀλ Bfᵀμ 0这就是“阻塞价格从能量价格里剥离”的数学本质对pg求导得到c Cgᵀλ - ν_min ν_max 0机组上下限的乘子ν直接进入该机组的边际成本。换句话说LMP由三部分构成系统能量价格、阻塞价格、机组约束价格。最后一部分在yalmip自带的dual输出里经常被忽略也是论文分析的重点。电价构成对应约束KKT乘子系统能量价格节点功率平衡λ_balance阻塞价格线路潮流约束μ_line机组约束价格机组出力上下限ν_pg2.3 为什么作者用手动对偶而不是直接调dualyalmip确实提供了dual函数可以取约束的拉格朗日乘子。但实际用起来有坑乘子的符号方向容易搞反而且部分求解器在默认设置下不导出对偶变量。更关键的是dual取出来的值依赖求解器内部对偶变量的标记约定不同版本的cplex也可能给出不同的正负号。我在帮别人debug时见过最典型的情况cplex里目标函数写的是minyalmip转过去自动把等式规范化乘子符号跟自己在纸上推导的正好差一个负号。这份资源采用手动对偶、再化为矩阵形式求解好处有三个。一是每一步KKT方程都有物理对应写报告时可以直接引用二是乘子的正负号完全由自己的符号约定决定不会出现“电价少个负号”这种玄学问题三是矩阵形式可以直接在MATLAB里验证互补松弛条件——某条约束不起作用时乘子必然为0。这才是分析LMP的正规做法也是这份资源值得复现的核心原因。2.4 单时段模型为什么反而适合学习有人会问都电力市场了怎么不加爬坡约束摘要里明确写了“单时段未考虑爬坡”这不是缺陷而是教学上的有意选择。爬坡约束的KKT乘子带有时间耦合性质它把相邻时段的出力变量绑在一起拉格朗日函数里多了跨时段项手动对偶写出来的不再是静态方程组。而单时段模型下所有约束都是纯代数等式/不等式KKT条件退化为线性互补问题。在最优解处互补松弛条件可以简化为“先判断哪些约束激活、哪些不激活再把非激活乘子置零”最后只需要解一个线性方程组。dual.m里那个矩阵形式就是这么来的。换句话说这份资源让你看到KKT最干净的样子先判定约束状态再拼矩阵最后解方程。搞懂了单时段再回去看多时段模型爬坡约束就是把矩阵加宽、乘子加多思路完全一样。3. 跑通前的准备yalmip与cplex的配置和算例文件结构3.1 安装与验证求解器先说安装。yalmip直接去官网下载zip解压后添加到MATLAB路径即可不需要编译。cplex建议装IBM的Community Edition——免费、支持小规模模型1000个变量以内对5节点出清模型绰绰有余。注意cplex装完以后MATLAB路径要单独加一次因为cplex的MATLAB接口是独立目录。% 添加yalmip路径改成你自己的解压路径 addpath(genpath(D:\tools\yalmip)); savepath; % 添加cplex的MATLAB接口路径 addpath(C:\Program Files\IBM\ILOG\CPLEX_Studio\cplex\matlab\x64_win64); savepath;这段代码的作用是把两个工具路径写进MATLAB默认路径以后打开MATLAB不用重新添加。如果你用的是macOS或Linuxcplex路径结构略有不同一般在/opt/ibm/ILOG/CPLEX_Studio下找cplex/matlab目录即可。装完以后先做一个最小验证确保cplex能被yalmip识别% 测试yalmip能否找到cplex ops sdpsettings(solver,cplex,verbose,0); x sdpvar(1,1); optimize([x 1, x 2], -x, ops); % 最大化x期望输出2 value(x)如果value(x)输出2说明求解器配置成功。如果报错“Could not find the solver”重点检查cplex的MATLAB接口路径是否添加成功以及MATLAB是32位还是64位——Community Edition只提供64位接口。还有一点容易踩如果机器上装了多个求解器yalmip可能默认挑别的最好每次都显式指定solver,cplex。3.2 资源文件结构与数据组织这份资源解压后四个关键部分文件角色内容case5.m主程序定义5节点系统数据、构建出清模型、调用求解器dual.m对偶求解手写KKT方程并矩阵化输出LMP及各类乘子pri.m原问题输出输出机组出力、相角、线路潮流、总成本报告.doc复现报告机组运行约束对LMP影响分析的图表和结论我一般把case5.m当主入口工作区变量保留再依次调pri和dual。数据组织上常见做法是直接在case5.m里写一个子函数或脚本片段定义系统参数。5节点算例典型的参数结构如下节点机组边际成本($/MWh)出力上限(MW)负荷(MW)1G11510002G220100203---804---1005G3251000总负荷200MW三台机组总上限300MW无阻塞时边际出清价格应该是20$/MWhG1满发100G2满发100G3不出力。如果某条关键线路阻塞G1的廉价电送不到负荷中心G3被迫开机LMP就会抬高到25甚至更高。这个“从20变25”的过程就是论文核心结论要解释的现象。3.3 第一次跑通的步骤第一次运行建议不要直接F5按顺序逐步执行case5; % 构建模型并求解 pri; % 查看原始最优解 dual; % 计算KKT乘子输出LMP注意这里是一行一行在命令行跑不是写成脚本一次运行。因为dual.m要读取case5求解后留在工作区的变量比如pg、theta、约束句柄。如果一次性F5某些中间变量可能因为clear被清掉。跑通后先检查两件事一是求解状态sol.problem等于0才说明求解成功二是总成本数值在刚才的参数下应接近4000$/h100×15100×203500加上网损修正。如果sol.problem为1表示不可行优先检查负荷与总装机容量是否匹配。跑通后单独运行dual.m对比节点电价在无阻塞场景各节点LMP应该都等于20$/MWh如果所有节点电价都一样不要急着认为代码错了——先看线路潮流是否达到限值如果所有线路都有裕量全网同价其实是正确结果。接下来再把某条线路的Fmax改小比如从400MW改成120MW你会发现LMP立刻分叉这就是论文要复现的核心画面。4. 核心代码拆解case5.m与dual.m的KKT矩阵是这样拼的4.1 决策变量与直流潮流建模先看决策变量定义。采用单时段直流潮流DC-OPF节点功率平衡用B矩阵电纳矩阵乘相角来表示% 决策变量 pg sdpvar(ng, 1, full); % ng3机组有功出力 theta sdpvar(nb, 1, full); % nb5节点电压相角 % 直流潮流平衡方程B*theta Cg*pg - Pd % B: 节点导纳矩阵实部nb x nb % Cg: 机组-节点关联矩阵nb x ng % Pd: 节点负荷向量nb x 1 balance B * theta Cg * pg - Pd;说明balance这条等式约束就是之后LMP的来源它对应的拉格朗日乘子λ就是你想要的节点边际电价。所以在后续dual.m里一定要保留balance这个约束句柄的引用不要重建模型。如果程序里把约束写成cell数组再合并建议用cons{1} balance这种方式保存乘子提取时才好定位。B矩阵和Cg矩阵怎么来的常见做法是手写。5节点系统的B矩阵由线路电抗求逆再组装Cg矩阵在节点i有机组时对应位置为1否则为0。如果从MATPOWER的mpc结构转换可以直接用makeBdc和关联矩阵生成但这个资源不依赖MATPOWERcase5.m里应该已经写好了组装逻辑。4.2 约束条件的yalmip写法机组上下限、线路潮流、平衡节点相角约束都要显式建模% 机组出力上下限 cons [cons, Pmin pg Pmax]; % 线路潮流约束f Bf * theta限值Fmax cons [cons, -Fmax Bf * theta Fmax]; % 平衡节点相角固定为0防止B矩阵零空间导致奇异 cons [cons, theta(ref) 0];参数说明Pmin和Pmax是ng×1向量对应3台机组的出力范围一般下限取0上限取各机组额定容量Bf矩阵是nl×nb的支路-节点关联矩阵第l行描述线路l两端相角差乘以1/x_lFmax是nl×1的线路潮流上限向量。这里有个细节线路潮流约束如果不加模型退化为经济调度ED节点电价必然全网同一加上且阻塞生效才出现LMP分叉。所以做敏感性分析时Fmax是最好用的旋钮。求解部分代码如下% 设置求解器为cplex关闭冗余输出 ops sdpsettings(solver,cplex,verbose,1); % 求解目标函数为总发电成本最小 sol optimize(cons, [15 20 25] * pg, ops); % 取出最优解 pg_opt value(pg); theta_opt value(theta); cost_opt value([15 20 25] * pg);说明optimize三个参数分别是约束、目标、设置。目标函数写成向量乘决策变量的形式[15 20 25] * pg就是总燃料成本。求解完用value()取出数值这些数值后续要喂给dual.m用。4.3 手动对偶dual.m的矩阵拼装这是整个资源的精华。在最优解处拉格朗日函数对各变量求偏导为0。dual.m里不再调求解器只做三件事判定激活约束、把非激活乘子置零、拼KKT矩阵解线性方程组。%% dual.m —— 手动KKT对偶输出LMP % 已知最优pg、theta来自case5的工作区 % 第一步判定激活约束用容差1e-6避免数值误差 is_pg_up_binding (pg_opt Pmax - 1e-6); % 机组达到上限 is_pg_lo_binding (pg_opt Pmin 1e-6); % 机组达到下限 is_line_binding (abs(Bf*theta_opt) Fmax - 1e-6); % 线路阻塞 % 第二步拼接KKT矩阵 % 对pg求导的方程c Cg*lambda - nu_min nu_max 0 % 对theta求导的方程B*lambda Bf*mu theta_ref乘子 0 % 以等式约束乘子lambda、线路乘子mu、机组上下限乘子nu为未知数 % A矩阵行数 ng nb列数 nb nl 2*ng A zeros(ng nb, nb nl 2*ng); rhs zeros(ng nb, 1); % 对pg求导的KKT行 A(1:ng, 1:nb) Cg; % lambda的系数 A(1:ng, nbnl1:nbnlng) -eye(ng); % nu_min的系数激活时为-1 A(1:ng, nbnlng1:end) eye(ng); % nu_max的系数 rhs(1:ng) -[15; 20; 25]; % 目标函数导数取负 % 对theta求导的KKT行 A(ng1:ngnb, 1:nb) B; % lambda的系数 A(ng1:ngnb, nb1:nbnl) Bf; % mu的系数 % 平衡节点相角约束产生的乘子lambda_ref A(ref, end) 1; % 在平衡节点对应行补一个单位列 rhs(ng1:ngnb) 0; % 第三步对非激活约束的乘子列做置零处理 % 线路非阻塞 - mu0机组未达限 - nu0 for l 1:nl if ~is_line_binding(l) A(:, nbl) 0; % 该列置零等价于强制mu(l)0 end end for i 1:ng if ~is_pg_up_binding(i) A(:, nbnlngi) 0; end if ~is_pg_lo_binding(i) A(:, nbnli) 0; end end % 第四步解方程 x A \ rhs; LMP x(1:nb); % 前nb个分量就是节点边际电价这段代码的逻辑说明A矩阵的行数是“决策变量个数”等式是“最优性条件”列数是“所有乘子”。把非激活约束对应的列整列置零等价于手动施加互补松弛条件。最后A \ rhs是MATLAB左除解一个线性方程组得到的就是全部乘子。LMP直接取前nb个分量因为节点功率平衡约束的乘子恰好排在矩阵最前面。这段代码我每次看都觉得思路干净。但要注意容差1e-6的取值不能太大否则会把本应激活的约束误判为非激活也不能太小否则数值噪声会让约束状态抖动。5节点小算例里1e-6稳妥实际工程系统建议按基值的1e-4~1e-5来设。4.4 结果如何对应报告结论跑完dual后把LMP与pri里输出的机组出力放一起看。如果G1达到Pmax且其所在节点LMP大于15$/MWh说明机组约束把节点电价抬高了——这正是《机组运行约束对机组节点边际电价的影响分析》一文的核心结论。报告.doc里应该有对应的对比图表复现时习惯在MATLAB里画两幅图一是线路潮流阻塞分布的柱状图二是各节点LMP柱状图。论文里“机组运行约束对LMP的影响”就能直接对应到算出来的数字上。再补一个操作细节如果只想快速看LMP随阻塞程度的变化把Fmax从400MW逐步降到80MW循环跑case5和dual把每次的LMP存下来画曲线一条经典的“电价分叉”图就出来了。这个过程全程不需要改模型结构只用改参数非常适合写进实验报告。5. 避坑指南算节点边际电价最容易翻车的五个细节5.1 yalmip的dual取出来全是0或NaN现象用dual(balance)取节点功率平衡乘子结果要么全是0要么是NaN没法用。原因yalmip的dual函数对求解器有要求。cplex默认用barrier方法求解LP时对偶变量不一定会完整回传到yalmip的约束句柄里另一类是模型里存在冗余约束cplex预处理阶段直接消掉了对偶变量。解决别在yalmip的dual上死磕。直接用dual.m的手动KKT矩阵方案或者先把cplex的模型导出来[model, recovery] export(cons, obj, ops)看model里有没有对应的quad或row对偶信息。这个坑是dual.m存在意义的一半——正常情况下手动对偶永远拿得到乘子。5.2 LMP出现负值且数值异常大现象节点电价算出来是负的甚至是-1000这种量级明显不合理。原因符号约定反了。KKT里等式约束的拉格朗日项写成λᵀ(Bθ - Cg·pg Pd)那么对θ求导时Bθ项带正号如果有人写成λᵀ(Cg·pg - Pd - Bθ)所有乘子全局变号。还有一种情况是yalmip里等式约束写反了比如把B*theta Cg*pg - Pd写成Cg*pg - Pd B*thetayalmip内部做规范化时也可能翻转符号。解决统一的校验手段是扰动法第6章会写。先用小步长扰动某个节点负荷看总成本增量与dual解出的LMP对比符号。符号反了的话只需把KKT方程里对theta求导那行整体乘-1或者在拼A矩阵时把B改成-B问题立刻消失。5.3 KK T矩阵奇异A \ rhs报错现象dual.m跑到A \ rhs时MATLAB提示“Matrix is singular or nearly singular”。原因平衡节点相角固定约束对应的乘子漏加了。B矩阵本身是不可逆的零空间对应全1向量如果不固定参考相角theta优化不唯一KKT矩阵里Bᵀλ 0这条方程也不满秩。解决在拼矩阵时保证A包含“theta(ref)0”约束对应的乘子列也就是在ref节点那一行补一个单位阵的列。另外检查变量定义里theta是否用了full类型如果默认是sdpvar的稀疏结构某些情况下矩阵拼装索引会错位。5.4 全网LMP全相等论文结论复现不出来现象case5跑完dual输出5个节点电价全相等都等于系统边际电价和论文里“阻塞导致LMP分叉”对不上。原因线路没有阻塞。这是最常见也最容易误判的情况。很多人以为只要模型里有线路潮流约束就会分叉但实际上只有约束“卡住”才有非零乘子。Fmax给得太大线路潮流距离限值很远阻塞乘子μ全为0LMP自然全网一致。解决把某条关键线路的Fmax人为调小比如从400MW改成120MW再跑一遍。看线路潮流是否达到120MW如果卡住dual里的is_line_binding会标记为trueLMP立刻分叉。调参数的过程建议记录在报告里作为敏感性分析顺便证明你理解阻塞对LMP的影响机制。5.5 cplex求解提示infeasible但模型看着没问题现象sol.problem等于1或者命令行直接提示“Infeasible”但约束条件都是一般的上下限逻辑上想不出哪里冲突。原因多数是两类问题。一是总负荷大于等于总装机上限比如负荷200MW但Pmax之和只有180MW必然无解二是某些线路的Fmax设成了0相当于禁止该线路送电如果这个线路又是某负荷节点的唯一供电路径也会无解。解决先算sum(Pmax)和sum(Pd)确认有裕量再把所有Fmax统一放大100倍跑一次如果此时变成可行说明问题出在线路潮流上最后逐个缩小Fmax用二分法定位是哪条线路卡住。这种方法虽然土但定位效率比看约束推导快得多我几乎每个模型都先用它排除数据错误。6. 验证影子价格正确性用扰动法给LMP做体检KKT手动对偶拼矩阵最怕的不是不会拼而是拼出来自我感觉良好、实际某个符号或索引错了。所以每次算完LMP我都会额外跑一个独立校验扰动法。原理很简单LMP的定义就是“该节点增加单位负荷后系统总成本的边际增量”。直接在最优点把节点i的负荷加1MW重新求解原始问题看总成本涨了多少这个增量就应该等于LMP(i)。% 扰动校验节点4负荷1MW Pd_orig Pd; % 保存基准负荷 Pd(4) Pd(4) 1; % 扰动节点4 new_cons replace_cons_load(cons, Pd); % 重建约束负荷变为Pd sol2 optimize(new_cons, obj, ops); deltaC value(obj) - cost_opt; % 成本增量 fprintf(LMP(4) from KKT: %.4f $/MWh\n, LMP(4)); fprintf(LMP(4) from perturbation: %.4f $/MWh\n, deltaC);如果两个数值对得上误差在1e-3以内说明KKT矩阵里节点4对应的乘子符号和大小都对。对每个节点做一遍这个校验基本能覆盖矩阵拼装中的所有常见错误。注意扰动步长不要取太大5节点系统里1MW大约占系统负荷的0.5%足以保证边际价格线性区间如果步长取到50MW可能跨过某个约束的激活点LMP跳到另一段扰动法和KKT结果对不上那不是代码错是数学模型本身发生了跳变。扰动法失效的场景还有一个节点电价存在断点。比如节点负荷增加1MW恰好让某条线路从非阻塞变成阻塞阻塞乘子从0跳到一个正值总成本增量就不是“边际”而是“分段”。这种时候扰动法给出的结果是一个步进增量跟KKT的边际乘子天然不相等。碰到这种情况不用慌把扰动步长缩小到0.1MW甚至0.01MW再试如果缩小后仍然对不上再回头检查矩阵——大概率是某条线路的激活状态判定错了。从那以后我每次跑完节点电价出清都会强制走一遍扰动校验五个节点全部对比一遍确认“乘子与扰动一致”才敢把结果写进报告。这个习惯帮我拦住过至少三次符号反了的低级错误也让我在答辩时能理直气壮说一句“LMP经过扰动验证”。方法土但真能救命希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑