计及碳排放交易及多种需求响应的微网/虚拟电厂日前优化调度(Matlab代码实现)
最近在做微网/虚拟电厂的日前优化调度课题时,发现很多刚接触这个方向的朋友都有同样的困惑:文献里动不动就是“计及碳排放交易”“多种需求响应”,但是翻开论文一看,公式符号满天飞,真正落到Matlab代码层面怎么建模、怎么约束、怎么调求解器,几乎没有一篇文章讲透。
这篇文章我想用我自己搭的一套日前优化调度模型作为主线,把碳排放交易机制和需求响应的建模过程、YALMIP求解器配置、常见报错排查这几个环节完整捋一遍。所有代码思路都是实测能跑的,参数部分我给了比较常见的取值,你拿到之后改改数据就能套到自己课题里。
这套代码适合电力系统、新能源、智能电网方向的研究生,也适合刚进综合能源、微网调度岗位的工程师。如果你只是想把论文复现出来然后毕业,那我的经验是一步到位把碳排放交易和需求响应同时做进去,比先做基础场景再加功能要省力得多,因为后面加的每一次改动都会牵动约束条件和目标函数的大改。
1. 我先说清楚这个项目到底在做什么
1.1 微网/虚拟电厂的日前调度是个什么问题
所谓日前优化调度,就是在已知明天光伏出力曲线、负荷曲线、电价曲线的前提下,提前一天决定每个时段各类设备的出力情况,使得整个系统在满足供电平衡和其他运行约束的前提下,运行成本最低。
这是个典型的多时段耦合优化问题。从数学上讲,它属于混合整数规划问题,因为储能充电/放电状态、机组启停、需求响应是否启用这些决策变量要么是0/1整数变量,要么是分段变量。你在Matlab里用YALMIP写模型、调Cplex或者Gurobi求解,本质上就是把这个工程问题转成标准数学规划问题来解。
微网和虚拟电厂在日前调度层面的建模结构高度相似:都是把光伏、风电、燃气轮机、储能、负荷以及跟大电网的交互功率放在同一套优化框架下,只是虚拟电厂的规模更大、分布式资源分布更分散,但调度模型的数学内核基本一致。
1.2 为什么要把碳排放交易放进去
我接触过不少做微网调度的研究生,第一版模型往往只考虑电费成本:向电网买电要花钱、燃气轮机发电烧气要花钱、储能和光伏几乎零边际成本,于是优化器就会拼命用光伏和储能,多余的电卖给电网,电不够再买,成本最低。
这套逻辑本身没问题,但是放到“双碳”背景下就不够看了。因为你买的电来自大电网,大电网的火电机组占比高,你买1度电其实就相当于在承担对应的碳排放;你的燃气轮机发电同样产生碳排放。如果把碳排放成本忽略,调度结果可能会过度依赖外购电甚至燃气轮机,导致整个微网的名义电费低,碳账却很难看。
碳交易机制本质上就是把碳排放这个外部性成本内部化。在模型里,调度中心根据上级主管部门核定的碳排放配额,如果实际排放量小于配额,把多余的配额拿到碳市场卖掉赚钱;如果实际排放量超过配额,必须到碳市场购买额度。这一卖一买之间的价差,直接进入目标函数的成本项,从而影响各设备的出力优先级。
1.3 需求响应:负荷侧不再是“一言既出驷马难追”
传统调度模型里,负荷曲线是刚性的,给定的值就是必须满足的值,优化器只能被动匹配。而需求响应把一部分负荷从“被动”变成了“主动”,让负荷侧参与系统调节。
我对需求响应的理解可以打个比方:原来的负荷是去餐厅点菜,服务员说“菜单上有啥你点啥,后厨按顺序做”;需求响应相当于餐厅给了你一个“提前预约折扣菜单”,你如果愿意把用餐时间从七点高峰改到九点,或者点某个套餐时减掉一道菜,餐厅给你打折。对微网来说,这个“折扣”就是给用户的激励补偿。
在模型里,需求响应分为两大类:价格型需求响应和激励型需求响应。前者通过电价信号引导用户自发改变用电行为,后者通过直接补偿方式让用户承诺削减或转移负荷。这两类响应在数学建模里的表达方式不同,我后面逐个拆开讲。
2. 把碳排放交易装进优化模型的具体做法
2.1 碳排放配额与成本折算的基础逻辑
碳排放交易加入调度模型的第一步,是算清楚系统一天的碳排放总量。我在代码里把碳排放来源分成两部分:外购电力的间接排放和燃气轮机的直接排放。
外购电力的碳排放计算比较简单:每个时段从电网购电的电量乘以电网的碳排放因子,再累加24个时段。电网碳排放因子是个经验或统计数据,不同地区差异很大,我在模型里取0.9689 kg CO2/kWh,这个数值基于国内某区域电网的平均排放水平,你可以根据自己课题所在地区的公开数据替换。
燃气轮机的排放则是输出电功率乘以机组碳排放强度。单台燃气轮机的碳排放强度通常取0.6~0.8 kg CO2/kWh左右,我用了0.7,同时乘上一个天然气的碳氧化因子校正。
有了总排放量E_total之后,下一步是配额。我在模型里用的是基于外购电量为基准的配额分配方式:配额 = 外购电量 × 配额系数。这个系数取0.7左右,代表“如果你从电网买电,只有70%的排放量被计为配额”,你少用了电网电就少拿配额,多用了就要额外买配额。这样设定的目的是鼓励微网自产自消,减少对大电网的依赖。
2.2 阶梯式碳价模型
很多初版论文用的是线性碳价模型:超出配额的部分按一个固定单价购买,低于配额的部分按同样单价卖。线性模型好处是方便求解,缺点是不符合碳市场的实际调节逻辑。
我在代码里用了阶梯式碳价:排放量超出配额越多,超出部分对应的碳价越高。这样做更贴近真实碳市场“超额排放的惩罚递增”原则,也能防止模型出现“为了多卖碳而故意降低发电量”的失真行为。
具体的分段方式是我在代码里定义的向量参数:超出配额在200kg以内按0.3元/kg计算,200kg到400kg之间按0.45元/kg计算,400kg以上按0.6元/kg计算。这段用YALMIP写的时候并不需要自己手动分段,用变量拆分加约束的办法可以实现,或者直接借助yalmip内置的分段函数指令,两种我都测试过,都能跑到全局最优解。
为了提升能效和减少碳排,我还引入了碳捕集设备运行成本,这里就涉及到一个权衡:碳捕集装置本身要耗电,如果碳价不够高,优化器根本不会让碳捕集装置运行太多;如果碳价足够高,碳捕集就会投入更多比例。这是很好的灵敏度分析点,论文里画碳价从低到高的情景对比图非常合适。
2.3 碳交易机制在目标函数里的呈现位置
碳排放成本必须写成目标函数的一个显式项。我在代码里这样组织目标函数:
Total_cost = 外购电成本 + 燃气轮机燃料成本 + 运维成本 - 售电收益 + 碳交易成本 + 需求响应补偿成本 + 碳捕集成本
其中碳交易成本这一项,如果实际排放小于配额就是负值,相当于收入;如果大于配额就是正值,相当于支出。
这里有个容易踩的坑:如果你把碳交易成本写成max(0, E_total - E_quota) × price,YALMIP就不好直接处理了。解决办法有三种,一是引入辅助变量加约束表示max项,二是用阶梯分段方式建模,三是直接把非负性放给模型。我代码里写的是辅助变量加约束的方式,通篇是线性约束配二进制变量,模型规模也不大,Cplex秒解。
提示:凡是涉及max/min的项,拿到YALMIP里尽量不要直接写max函数,因为部分版本的老求解器转不出来。用辅助变量加约束展开,逻辑清晰还不会报错。
3. 多种需求响应在模型里的实现与代码细节
3.1 价格型需求响应的弹性矩阵建模
价格型需求响应(PBDR)是基于电价变化对负荷进行柔性调整的方式,我用的模型是自弹性系数和交叉弹性系数矩阵。
自弹性系数描述的是当前时段的负荷对当前时段电价的敏感程度,通常取值在-0.2到-0.5之间,意思是电价上涨1%,该时段负荷下降0.2%到0.5%。交叉弹性系数描述的是当前时段负荷对相邻时段电价的敏感程度,比如晚高峰电价高了,用户会把部分用电转移到深夜低谷时段,这个系数通常是正值。弹性矩阵把24个时段两两之间的关系都刻画出来了,是一个24×24的矩阵。
在代码实现上,PBDR对负荷曲线的修正结果本质上是一个线性变换:负荷响应量 = 弹性矩阵 ×(当前电价相对基准电价的变化率)。我沿着这个思路把调整后的负荷曲线计算出来,作为新的负荷输入到功率平衡约束中。这样做有一个隐蔽的好处:它没有引入额外的0/1变量,对求解效率几乎没影响。
3.2 激励型需求响应的约束与补偿
激励型需求响应(IBDR)跟价格型最大的区别是,它有一个明确的“可中断/可削减负荷量”上限,用户愿意响应,是因为收到了真金白银的补偿。在模型里我需要同时决定每个时段用户响应了多少负荷,以及对应的补偿费用。
具体到约束条件,我设置了三个硬条件:可转移负荷在各时段的转移量不能超过该时段原始负荷的一定比例(比如30%);可中断负荷的调用量不能超过系统设定的最大可中断容量;全天被转移的总负荷不能超过总可转移负荷的容量上限。
补偿费用我采用了线性补偿函数,也就是说每响应1度电给一个固定补偿单价。如果你想做更高阶的模型,可以改成阶梯补偿,响应量越大,边际补偿单价越高。不过线性补偿在实际运行中已经足够说明问题了,而且参数少好调。
3.3 可转移负荷与可中断负荷的分开处理
可转移负荷和可中断负荷虽然都是需求响应,但建模思路完全不同,我一开始也混在一起写过,后来发现会带来解的不合理。
可转移负荷的特点是:用户的总用电量不变,但是用电时间可以挪。典型例子是洗碗机、洗衣机、电动汽车充电。这类负荷需要对“每天总转移量守恒”加约束,就是转入的量跟转出的量在24小时内必须平衡,否则模型会通过“凭空增加或减少负荷总量”来占便宜。
可中断负荷的特点是:负荷被削减了就是削减了,全天总用电量跟着减少,用户拿到补偿。典型例子是空调在高峰期的轮停、工业产线的部分停机。这类负荷只需要限制削减量的上限以及最小持续削减时间,不需要全天总量守恒。
我在这套模型里把两类负荷分别定义了两个决策变量组,互不干扰。这样不仅模型更清晰,调参的时候也方便:你可以只放开可转移负荷的比例看效果,也可以同时放开两类负荷看系统调节能力提升多少,论文里的对比方案一下子就多出来了。
4. Matlab代码实现的完整链路
4.1 整体代码结构与数据输入
代码文件我按模块拆成几块的思路组织,我看不少博主喜欢把几百行塞一个main.m,调试的时候眼睛都要花了。我用的结构是四个文件:初始化参数文件、模型构建文件、求解器配置文件和结果可视化文件。
初始化参数文件负责载入基础数据:各时段光伏出力和负荷预测值、分时电价、天然气价格、储能参数、机组参数、碳排放因子和碳价分段数据。这些数据全部定义成向量或结构体,方便后续在约束里直接引用。
模型构建文件是核心,里面用sdpvar定义决策变量,然后用约束和goal的写法把目标函数和约束条件喂给YALMIP。我建议所有决策变量集中定义,不要散落在文件各处,否则改模型的时候容易漏改约束。
求解器配置文件负责确定用哪个求解器、是否显示求解日志、求解精度等。结果可视化文件画负荷曲线、储能SOC曲线、机组出力堆叠图、碳成本柱状图等。
4.2 YALMIP建模与求解器配置
如果你问我Matlab里做优化调度用哪一套工具最省心,我的答案始终是YALMIP + Cplex或者YALMIP + Gurobi。YALMIP把建模和求解分离,你只需要用sdpvar定义变量、用约束定义等式和不等式、用optimize函数求解,具体的求解器怎么处理MATLAB矩阵都不用关心。
我代码里给了一个自动检测求解器的小逻辑:如果检测到Gurobi,优先用Gurobi;如果没有,就用Cplex;两个都没有就提示你先装好任意一个商业求解器。原因很简单,咱们这个模型里有0/1变量,属于MILP问题,免费的求解器(比如linprog)解不了整数变量,或者即使能解也慢到怀疑人生。
求解器的关键参数我也建议设一下:MIP间隔(MIPGap)设置为0.01%,即要求最优性间隙不超过万分之一;输出日志打开,方便看求解器的迭代过程和是否找到全局最优解。对于24时段的微网调度,数据规模其实很小,绝大多数情况下Cplex/Gurobi几秒钟就能出结果,MIPGap设紧一点也不会有性能压力。
4.3 核心约束的Matlab代码写法
功率平衡约束是一切的根基。每个时段,光伏出力加燃气轮机出力加储能放电功率加外购电功率加负荷响应削减量,必须等于刚性负荷加储能充电功率加向外卖电功率加碳捕集装置耗电。这里注意碳捕集耗电在有些论文里会被忽略,我一加上去就发现它对結果影响很大,系统相当于多了一个“电老虎”。
储能约束要处理三组关系:SOC的递推关系、充放电功率上下限、充放电状态互斥。SOC递推用经典公式:SOC(t+1) = SOC(t) + 充电效率×充电功率 / 容量 - 放电功率 / (储能容量×放电效率)。充放电互斥用两个二进制变量加一个约束:充电状态 + 放电状态 ≤ 1。
机组约束主要是出力上下限和爬坡约束。燃气轮机的爬坡速率通常设置为每小时额定功率的20%~30%,这是防止模型给出“这一时段满发、下一时段停机”这类物理上实现不了的方案。
4.4 结果可视化与对比
结果可视化我建议至少输出四张图:第一张是各时段源荷功率平衡堆叠图,能直观看到光伏、储能、外购电如何共同满足负荷;第二张是储能SOC曲线,可以确认儲能的充放电策略是否符合直觉;第三张是需求响应前后的负荷曲线对比,突出削峰填谷效果;第四张是碳成本和总成本的时段分布。
我在实际跑完代码之后,还会做一个基线对比:一组是“不考虑碳交易和需求响应”的纯经济调度,一组是“只考虑碳交易”的调度,一组是“碳交易加需求响应全上”。三组结果表放在一起,碳交易如何改变电源结构、需求响应如何改善负荷曲线、总成本如何变化,一目了然,这一张对比表几乎是论文结果部分的骨架。
5. 参数敏感性分析与结果解读
5.1 碳价变化对调度结果的影响
碳价是整个碳排放交易机制里最核心的变量。我做一个典型情景测试:碳价从0.2元/kg逐步升到0.8元/kg,观察燃气轮机出力、外购电量、碳捕集运行比例和总成本的变化。
实测的结果很有代表性:碳价低的时候,优化器偏向多从电网买电,因为电网电便宜,碳成本不痛不痒;碳价升到中间段,燃气轮机开始增加出力,因为燃机虽然燃料成本高,但碳排放强度低于电网平均排放因子,算上碳成本反而更划算;碳价再继续升高,碳捕集设备开始大规模运行,同时需求响应的调用量也明显上升。
这个结果说明碳价并不是越高越好,也不是越低越好,而是存在一个“合理区间”。如果你论文里需要提出一个“建议碳价水平”,可以画碳价——系统总成本——碳排放量的二维关系图,找碳减排效益边际递减的拐点。
5.2 需求响应参与度对削峰填谷效果的影响
需求响应参与度我通过两个参数控制:可转移负荷比例和可中断负荷上限。分别设置5%、10%、20%三档,观察负荷曲线形态变化。
5%档位的时候,削峰效果很不明显,只有高峰时段削掉一小块;10%档位时能清楚看到晚高峰的峰值被削平了一些,填谷效果也开始显现;20%档位时系统调节空间一下子变大了,所有时段基本都能把负荷平整到比较合理的水平,但此时补偿成本也在显著上升。
这给工程应用的启发是:需求响应不是越多越好,它跟碳价一样,存在边际效益递减。系统运营商需要根据峰谷差电价和补偿单价之间的关系确定最优的响应参与度,不能说定个50%就一定好,算完账再看。
5.3 储能与需求响应的协同关系
储能和需求响应都是灵活性资源,但它们的成本结构不同。储能的成本主要在投资折旧和循环寿命损耗,需求响应的成本主要是用户补偿费用。
同场景下对比测试时我发现,优化器会优先调用价格更低的一方。碳价高的时候,需求响应会被更多调用,因为需求响应直接减少了负荷峰值,顺带减少了高碳价下的外购电成本;碳价低的时候,儲能反而会被优先调度,因为储能没有碳排放问题,而需求响应还要付补偿费。
这个协同关系在你写论文的时候是个很好的讨论点。可以画一张“储能SOC曲线在不同碳价下的变化”对比图,你会发现碳价升高时储能在高负荷时段的放电深度在增加,本质上是碳价通过成本传导改变了灵活性资源的调用优先级。
6. 常见问题与调试实录
6.1 求解器报错“无解”的原因排查
项目做下来,我遇到最多的报错就是“求解器返回无解状态”。每次看到这个问题,我的排查清单是固定的,按顺序查,基本一两轮就能定位:
第一步检查功率平衡约束的变量维度是否匹配,24个时段的向量长度至少有一个写错了数字。第二步检查储能SOC初始值,如果设置SOC(1)=0.2但容量和功率约束不允许这个电量在第一个时段满足,也会无解。第三步检查需求响应的可转移量上限设置,如果可转移比例太小但负荷峰谷差很大,约束会发生冲突。第四步检查碳捕集耗电是否让系统在某些时段总可用电力低于刚性负荷需求,这种情况相当于把系统约束锁死了。
我这套代码里有一处特别容易踩坑:燃气轮机的爬坡约束在24小时的首尾时段如何处理。如果爬坡约束写成t从1到24,t=1时的上一步状态没有定义,很多初学者直接写循环从2开始就完事,但实际上t=1时段也应考虑机组启动爬坡能力。我的处理方式是假设t=1时段的前一状态为冷启动,给一个较小的起始出力上限,然后把约束循环从1开始数组索引对齐。
6.2 求解结果违反直觉怎么查
测试中经常出现一种情况:求解器明明报“最优解”,画出来却发现某个时段储能同时充电又放电,或者需求响应调用量为零但负荷曲线却变了,这时候大概率是模型约束写漏了或者写错了方向。
我自己的检查习惯是用Matlab做一次数值核对,俗称“手算验证”。随便抽一个时段,把光伏预测出力、燃气轮机出力、储能功率、购电功率、需求响应削减量相加,看是否严格等于负荷。如果差那么几个小数点的数值误差,说明约束写对但求解精度不够;如果差了几百千瓦,那就是约束漏写了或者变量用错了索引。
还有一次我需要提醒的情况是YALMIP的变量默认是连续实数,如果你不小心把本该定义成binvar的储能状态变量写成了sdpvar,优化的结果里会涌现出大量0.47、0.56这类“半开半关”的数值。这种结果画出来的SOC曲线很平滑但物理上根本不可行——储能不可能以47%的占空比工作。
6.3 调参建议:从基础模型到完整模型的推进路径
最后聊一下我个人的经验。很多朋友拿到代码第一件事就是打开参数表一顿乱改,以为把碳价调高就完事,这种思路对理解模型帮助不大。
我建议按三步走:第一步先把需求响应比例全部归零、碳价也设成0,跑一遍基础经济调度,确认各设备出力逻辑正常;第二步只把碳交易打开,观察燃气轮机和外购电的比例变化;第三步再打开需求响应,观察负荷曲线平移和削减效果。每步都对比输出表格和曲线,这才能感受每个机制单独和耦合的作用。
这样做还有个好处是,万一模型出问题,你能分清是碳交易模块的问题还是需求响应模块的问题,不至于一出错就在几百行代码里大海捞针。
项目整体框架就是这样一个从数学模型到代码实现再到结果分析的完整链路。我最后再留一句心得:做优化调度课题,公式推导和代码实现各占一半,但真正拉开差距的是“让模型正常运行并给出合理解释”的能力。很多人卡在模型能写出论文、代码却跑不出结果这一步,少看了很多有价值的规律。如果你也在推这个方向,建议务必先从简版模型跑通,再逐层加机制,这样每一步的改动都看得见、解释得通。