1. 模型定位:两级市场与消纳责任权重怎么走到一起的
电力市场方向的项目做到清洁能源消纳这一块,迟早会跟"消纳责任权重"这个词打照面。它本质上就是给"每个市场主体到底要消纳多少绿电"定一个硬性比例,不够就通过绿证或罚款来补。这个机制一旦进入市场出清模型,就不只是加个约束那么简单了,它会把全局的资源优化配置和每家售电公司自己的购电策略绑在一起,稍不留神就变成一个挺复杂的双层问题。我最近用Matlab把这一整套流程完整搭了出来,求解器用的Cplex,从建模、编码到调试踩坑,今天一次性讲透。
这套代码适合谁来参考?想复现电力市场出清、新能源配额制、绿证交易相关算例的同学,毕业论文需要可运行Benchmark的,或者工作中要给区域消纳权重算账做机制分析的朋友,都可以直接对着抄。模型规模不大,但框架完整,顺序出清版本一百多行Matlab就能跑通,进阶版(KKT双层)也能在这个基础上平滑升级。
1.1 两级市场的设计动机:全局配置与本地执行要分开
为什么非要做"两级",不能拉平成一个市场?核心原因是电力系统的资源优化配置是全局性的,但交易执行和责任考核是本地性的。风电、光伏大基地通常离负荷中心很远,一个交易中心想把所有细节全包进来,不仅模型复杂度爆炸,很多本地规则、个体偏好也没法在一个集中模型里表达清楚。
参考全球能源互联网理念来看,这里面最核心的思想是"清洁能源全球配置、各类电源互补互济"。落到电力市场运行上,实际操作就是先有一个全局出清层,负责把成本最低的电源组合算出来,同时确定全网的可再生能源消纳总量;然后再有一个本地执行层,让市场成员在自己头上的约束下做实际的购电和绿证交易。两级之间有清晰的价格信号和配额信号传递,所有市场成员的行动逻辑都能对上账,这比一个大一统模型更容易解释、更容易落地,也更容易编代码调试。
现实中很多市场也是类似的两级结构,比如省间-省内两级市场:省间层做资源大范围优化,省内层做交割和考核。这里顺便说一句,两级结构不是说上层"高级"、下层"低级",而是分工不同。上层负责把全社会成本压到最低,下层负责把责任和利益分到每个主体头上。所以建模时两层的目标函数和约束长得完全不一样,理解这一点对后面写代码很重要。
1.2 消纳责任权重到底是什么
消纳责任权重,通俗讲就是给用电方定一个"绿色用电"的下限比例。假设一个售电公司年用电量1000万度,被分配的消纳责任权重是20%,那它就要保证这1000万度电里有200万度来自可再生能源。这里可以用一个生活类比来帮助理解:有点像企业安排残疾人就业的规定——用人规模到一定数量,就必须按比例录用特定人员,比例不达标要么补齐、要么交钱。绿证就是这个"交钱"的通道。
在建模的时候,权重不会直接罚钱,而是通过两类机制成本化。第一类是绿电交易形成溢价,绿电比常规电贵,买了绿电等于付出了额外成本;第二类是绿证交易和惩罚机制,指标不够就要买绿证,绿证价格就成了一个影响决策的关键变量。这两类信号最终都会落到售电公司的购电成本函数里,所以模型里权重是用约束写的,但效果是靠成本体现的。
模型里权重通常要写两类约束:全网层面的整体消纳约束,和每个售电公司个体的责任约束。整体约束保证系统总体完成消纳目标,个体约束保证目标不是被少数主体"背走",而是公平分摊到每个市场成员。两级市场里,整体约束放在第一级,个体约束放下层或结算阶段,这个分工千万别搞反,否则容易出现"全网达标但某个售电公司严重不达标"的怪结果。
1.3 两级怎么交互:先出清、后分配、再结算
我实际跑通的基础版本是"顺序出清"框架。第一级由交易中心做全系统优化出清,决策各火电出力和可再生能源出力,约束包括机组上下限、可再生预测上限和整体消纳权重。出清完成后会得到一个系统边际电价,也就是功率平衡约束对应的拉格朗日乘子,这个价格是下层结算的基准,也是整个代码里最容易写错的地方。
第二级是每个售电公司拿到自己的负荷需求和在总可再生出力中的份额之后,分别核算常规购电费用、绿电费用和绿证缺口成本。看起来像账务处理,但因为可再生能源出力总额本身是上层决策的结果,所以下层受上层的配额影响非常大。如果某个售电公司分到的可再生电量少于自己应承担的责任,它就必须去买绿证;如果绿证价格很高,它也可以选择多买一点高溢价的绿电来替代绿证,这就形成真正的购电结构优化。
如果想让两级真正"打起来",也就是让下层的购电结构反过来影响上层出清,那就要用KKT条件把下层优化问题转成上层的一个约束,形成MPEC模型。这个我放在后面的进阶部分讲,基础版本先把顺序框架跑明白,已经能把很多配额机制的公平性、效率性问题看得清清楚楚。
2. 数学模型:两层各自优化什么、怎么耦合
2.1 第一级出清:目标函数与约束
第一级模型写成标准优化问题很清晰。决策变量有两类:火电机组的出力P_i,以及每个可再生能源电站(风电场、光伏电站)的出力Pw_r。目标函数是最小化系统总运行成本,火电用二次成本函数拟合,可再生能源发电成本设成低价或者固定成本:
min Σ_i (a_i * P_i^2 + b_i * P_i + c_i) + Σ_r (w_r * Pw_r)约束条件包括:
功率平衡: Σ_i P_i + Σ_r Pw_r = D_total [λ] 火电出力上下限: P_i_min ≤ P_i ≤ P_i_max 可再生预测上限: 0 ≤ Pw_r ≤ FW_r 整体消纳约束: Σ_r Pw_r ≥ α_total * D_total这里面要解释两个细节。第一,为什么可再生成本写得很低?在现货市场出清逻辑里,成本越低越优先,可再生故意设低价能保证它尽量多消纳,相当于模拟优先发电政策。但真正让可再生达到目标的不是这个低价,而是那条整体消纳约束。就算可再生成本不是全场最低,权重约束也会强制出力升到某个下限,这和现实中"保量保价"的机制是呼应的。第二,功率平衡约束旁边的[λ]就是系统边际电价的来源,它是这个等式约束对偶乘子。如果可再生出力上升导致火电被替换,λ会下降,而下层结算全都要根据λ来走,所以这一步算得准不准直接影响整个模型结果。
如果火电成本用二次函数,整个上层就是一个二次约束二次规划,Cplex可以直接解。但有些版本里为了跟混合整数机组组合配合,会把成本曲线分段线性化,用binvar做分段选择,那问题就变成MILP。两种我都试过,算小算例差别不大,但如果后面要加机组启停,建议一开始就用分段线性化,省得组合规模大了Cplex算不动。
2.2 可再生能源出力怎么分给每个售电公司
上层出清得到可再生能源出力总额后,怎么分配给各个售电公司,是整个模型里面机制设计味道最浓的一步。基础版我用的是按负荷占比分配:
y_m = (D_m / D_total) * Σ_r Pw_r好处是无歧视、公平、好实现,代码里就是一行矩阵运算。缺点也很明显:没有考虑不同售电公司之间消纳责任权重的高低差异。比如甲和乙两家售电公司负荷一样,分到的绿电也一样多,但甲权重15%、乙权重30%,那么乙很轻松、甲还差一大截,而乙可能是超额完成,超额部分又不能直接转让给甲,这就造成配额资源浪费。
进阶版可以改成按"责任缺口比例"分配:先初步分一轮,然后看哪些售电公司分到的量超过了自己的责任,把超额部分收回来按缺口大小补给还差得多的主体。这样做能显著降低整个系统的绿证采购总量,也更贴近实际市场中绿电溢价和绿证价格互相博弈的结果。再进一步,就是干脆把分配也当成决策变量,让每个售电公司自己申报、自己优化,底层通过价格信号达到均衡——这是后面KKT双层要干的事。
2.3 第二级:售电公司购电与绿证缺口核算
每个售电公司m的购电结构由三部分构成:常规电量、可再生能源电量、绿证。如果按基础版固定分配量y_m来算,目标函数就变成:
min λ * x_m + p_ren * y_m + p_cert * cert_m约束条件:
x_m + y_m = D_m cert_m ≥ α_m * D_m - y_m cert_m ≥ 0如果y_m是固定的,那这一层本质上只是核算,没有真正的优化空间。想让第二级有"优化"味道,就把y_m从固定值改成决策变量:系统出清只给出可再生可用量上限Y_max_m,售电公司在常规电价λ、绿电价格p_ren、绿证价格p_cert三者相对关系下,决定买多少绿电、买多少绿证。
这里有一个非常实际的逻辑:当p_ren比p_cert便宜的时候,售电公司肯定倾向于多买绿电来完成任务,哪怕绿电超过自己责任权重,也不会觉得亏,因为省下了绿证钱;反过来如果绿证很便宜,那大家可能就都去买绿证,顶格完成任务后就不再买绿电。这种博弈行为在第一级出清完全看不到,只有把第二级也做成优化问题才能体现出来。所以我的建议是:基础版可以用固定分配,但代码结构要留出"变量化"的余地,后面升级MPEC就不用推翻重来。
2.4 如果做成真正双层:KKT和MPEC思路
严格的双层模型长这样:上层决定发电计划,下层每个售电公司在给定价格下决定购电组合。下层要写成优化问题,上层在决策时要把下层的反应函数考虑进来。把下层LP用KKT条件替换,整个问题就等价转成一个带互补约束的单层问题,也就是MPEC。
KKT条件包括四块:稳定性条件(拉格朗日函数对变量求导等于0)、原始可行性、对偶可行性、互补松弛。前三个都是线性约束,麻烦的是互补松弛,形式上长这样:
0 ≤ cert_m ⊥ (p_cert - μ_m) ≥ 0互补松弛Cplex不能直接处理,需要引入一个大M整数变量把它线性化。对每对互补约束加一个0-1变量z,约束变成:
λ ≥ 0, g(x) ≥ 0 λ ≤ M * z g(x) ≤ M * (1 - z)这样MPEC就变成混合整数线性规划,Cplex可解。不过这个大M取值最坑,取太大导致数值病态、解出来全是噪声,取太小又把可行域削掉,优化直接无解。我的经验是先解一个不加互补约束的松弛版,看看对偶乘子的量级大概在多少,再把M设成比这个量级大一两个数量级。比如对偶乘子大概在几十,M取1000比较稳妥,而不是随手写个1e6。
3. Matlab + Cplex代码实现:从环境到核心代码段
3.1 环境配置:YALMIP和Cplex怎么装、怎么验证
这套模型用到了MATLAB加YALMIP加Cplex三个部分,安装顺序建议是:先装MATLAB,再装IBM ILOG CPLEX Optimization Studio,最后把YALMIP下载好并加进MATLAB路径。CPLEX装完后要确认它的版本和MATLAB版本兼容,比如CPLEX 12.10只支持到R2019a至R2021b的某些版本,装好之后如果yalmiptest里CPLEX显示Not found,大部分情况是版本不匹配,少数情况是没把CPLEX的MATLAB接口路径加对。
YALMIP其实只是一个建模层,它不负责真正求解,而是把模型翻译给Cplex。下载YALMIP之后,直接用:
addpath(genpath('D:\tools\yalmip')); % 改成你的YALMIP目录 savepath;然后在Matlab命令窗口跑一下yalmiptest,会列出可用的求解器。这里有一个细节,yalmiptest会跑一大堆测试,如果只关心Cplex,可以用yalmiptest('cplex')。运行的时候建议把Cplex的许可也确认一下,社区版有变量和约束规模限制,模型大了容易报错,如果项目比较大,用教育版或者商业版会省心很多。
还有一个小坑:MATLAB自带的linprog、quadprog会被YALMIP默认识别,如果没装Cplex,YALMIP也会自动退回到内点法求解,结果可能也能出来,但速度慢不少,而且很多MILP问题直接没法解。所以写代码时最好在sdpsettings里面明确指定'solver','cplex',万一Cplex掉线,至少能立刻发现,而不是默默用别的求解器算出个不太好使的结果。
3.2 算例数据准备:一组能直接跑通的小系统
为了让下面代码不悬空,我准备了一个小算例:4台火电机组、2个风电场加1个光伏电站、3家售电公司,总负荷600MW。这个规模Cplex社区版肯定跑得动,而且结果能手工验算。
火电机组参数如下:
| 机组 | Pmin(MW) | Pmax(MW) | a(元/MW^2) | b(元/MW) | c(元) |
|---|---|---|---|---|---|
| G1 | 50 | 150 | 0.020 | 20 | 100 |
| G2 | 40 | 120 | 0.015 | 25 | 80 |
| G |