☰
综合需求响应与阶梯碳机制的综合能源系统优化调度及Matlab复现
2026/10/3 4:15:39 网站建设 项目流程

这阵子集中复现了一批综合能源系统优化调度的论文,其中“综合需求响应与阶梯型碳机制下的综合能源系统优化调度策略”这个方向让我印象很深。标题看起来挺学术,拆开其实就三件事:让多能用户在价格信号下主动调整用电用热行为,给碳排放定一个梯度递增的价格,再把这两件事塞进一个混合整数线性规划模型里求解。很多刚接触这个方向的读者,一上来就卡在“需求响应怎么建模”“碳交易怎么线性化”“Matlab里到底怎么把约束写进YALMIP”这些环节。这篇文章我就把完整复现流程从头到尾记录下来,从系统建模、机制设计,到代码实现细节,再到我踩过的几个坑,一次性讲清楚,希望对正在做综合能源系统、电热耦合调度、论文复现的同学有帮助。

1. 先从这篇文献的研究内核说起

1.1 综合能源系统为什么要做“综合”调度

传统电力调度只需要盯住一条功率平衡关系,顶多考虑下机组爬坡和网络安全约束。但到了综合能源系统里,事情立刻复杂起来:电网、气网、热网在物理上通过热电联产机组、燃气锅炉、电锅炉、电转气等设备耦合在一起,任何一方的调节都会传导到另外两方。比如某个时段电价高、气价低,系统可能用燃气锅炉多产热;反过来热负荷高峰时,CHP机组的发电量会被“绑着”抬升,多余的电力只能卖给电网或者储能。这种强耦合特性决定了调度模型必须是“综合”的,不能只看电。

从复现角度看,这意味着你要构建的目标函数和约束条件必须能同时描述购电、购气、热平衡、储能状态、碳交易成本、需求响应收益。做代码的时候,模型变量是一批二维向量,约束本质上就是把这个耦合关系转成等式与不等式方程组。没有这一层背景认知,代码很容易写成“电是电、热是热”的孤岛,拼起来之后模型求解要么不可行,要么结果跟论文对不上。

1.2 需求响应和碳交易被纳入优化的方式

大多数人第一次读这类论文,都会问一个问题:需求响应明明是用户行为,怎么放进数学优化里?答案是把它“价格弹性化”。也就是默认用户用电用热对价格变化是有响应的,电价涨了用电量下降,电价跌了用电量上升;不同能源之间还有替代关系,电价涨得太厉害,一部分电负荷被热负荷或气负荷替代。把这些响应关系量化成弹性系数矩阵,就得到了需求响应后的负荷曲线,可以作为已知输入或作为变量参与优化。

碳交易机制的纳入逻辑稍有不同。系统每天运行会产生碳排放,外购电力、燃烧天然气都是排放源。论文会先按基准线法给系统分配一个免费排放额度,实际排放超过额度就按阶梯碳价购买配额。碳排放成本因此变成了一个分段线性函数,放进目标函数后,优化算法会自动在“多买碳配额”和“调整机组出力”之间做经济性取舍。这就是这类文献的核心机制:用价格信号同时引导用户侧和供给侧做出低碳选择。

2. 系统架构与核心设备建模

2.1 一个典型的电-气-热耦合系统长什么样

我在复现时采用的是一个比较标准的电-气-热综合能源系统拓扑。外部输入是电网购电和天然气网购气;内部设备包括热电联产机组(CHP)、燃气锅炉(GB)、电锅炉(EB)、电储能(ESS)、热储能(TSS),还可以加一段风机或光伏作为可再生能源;负荷侧是电负荷和热负荷,两者都可以叠加上需求响应措施。调度周期一般是24小时,步长取1小时,决策变量就是各设备在各个时刻的出力。

这个拓扑最大的好处是规模适中,既能体现多能耦合,又不会因为设备太多导致模型难以调通。复现论文的时候,我强烈建议先从这样的“最小系统”开始,先把代码跑通,再逐步往里加弃风弃光、冷负荷、电转气、网络潮流等复杂元素。一上来就堆一个大而全的模型,出问题时根本不知道是哪个模块写错了。

2.2 设备模型的数学写法与常见简化

设备建模是整个复现最基础的部分,我整理了一个常用设备简化模型表,复现时可以直接对照着写约束:

设备输入与输出关系关键约束
CHP机组消耗天然气,同时产电产热,H_chp = k_echp × P_chpP_chp上下限、爬坡约束
燃气锅炉消耗天然气产热,H_gb = η_gb × F_gb热出力上下限
电锅炉消耗电产热,H_eb = η_eb × P_eb耗电功率上下限
电储能SOC动态,SOC(t+1) = SOC(t) + P_ch × η_ch − P_dch / η_dch充放电功率上下限、SOC上下限、始末SOC相等
热储能蓄热/放热功率有上下限,储热罐容量有限类似SOC的蓄热量状态约束

需要特别说明的是CHP。实际CHP的发电与供热之间存在复杂的可行域,通常是一个多边形区域。但很多文献复现时会把电热比直接取为常数,即H_chp = η_hr × P_chp,这个简化可以接受,但你要在文中注明。如果你希望模型更精细,可以改成H_chp = a × P_chp + b,或者用一组线性不等式描述可行域,这样电热耦合程度会随工况变化,结果更像真实系统,代价是约束数量变多。

储能的建模有个地方特别容易出错:SOC变量个数。如果调度周期是T=24,SOC变量应该定义成1×25,让SOC(1)是初始时刻,SOC(25)是调度结束时刻。循环约束SOC(1) == SOC(T+1)表示一个完整的日运行周期后储能能量复原。很多复现失败就是因为SOC变量数量写成了24,最后时刻的连续性条件对不上。

2.3 系统平衡约束:电、热、气三张表

系统平衡约束是保证模型物理意义正确的核心。电平衡的表述是:购电 + 风电实际出力 + CHP发电 + 电储能放电 = 电负荷(需求响应后)+ 电锅炉耗电 + 电储能充电。热平衡是:CHP产热 + 燃气锅炉产热 + 电锅炉产热 + 热储能放热 = 热负荷(需求响应后)+ 热储能蓄热。气平衡相对简单,就是购气量等于CHP消耗和燃气锅炉消耗之和。

写约束的时候我习惯统一用“左边是供给侧,右边是需求侧”的格式,这样检查等式时不容易漏项。需要注意的是,设备之间的损耗系数、效率系数如果放在等式两侧,很容易出现单位错误。我的经验是先把所有设备功率统一成kW,天然气统一成kW等价热值,再往模型里写,避免出现“千瓦对千瓦时”的错位。

3. 综合需求响应和阶梯碳机制的数学化

3.1 综合需求响应的弹性矩阵建模

综合需求响应最常用的数学工具是价格弹性矩阵。假设系统有电、热两类负荷,对应的价格向量是电价和热价,那么响应前后负荷的变化量可以用下面的矩阵形式描述:

[ΔP_e / P_e0 ; ΔH_h / H_h0] = E × [Δρ_e / ρ_e0 ; Δρ_h / ρ_h0]

这里E是2×2弹性系数矩阵,对角元素是自弹性系数,非对角元素是交叉弹性系数。自弹性通常为负,含义是电价上升,用电需求下降;交叉弹性则可以刻画能源替代,比如电价升高时部分电负荷会转移到热负荷上。具体数值需要从论文或调研数据中取,复现时常用的取法是自弹性约−0.2左右,交叉弹性约0.1左右,不同论文差异较大,你以自己对应的文献参数为准。

价格已知时,响应后的负荷就是:

P_load_DR(t) = P_load0(t) × (1 + Σ(ε_ij × Δρ_j(t) / ρ_j0(t)))

实际复现过程中,日内电价通常是已知曲线,热价一般设定为常数,所以Δρ/ρ0是一个确定标量,需求响应量可以直接作为系数乘到负荷上,不需要增加决策变量,模型规模也就不会变大。如果论文里把售能价格也列为决策变量,那就意味着你要动态定价,模型会变成双线性或非凸形式,求解难度直接上升,后面我会单独讲这个问题。

3.2 阶梯型碳交易机制:配额、碳价与分段线性化

阶梯型碳交易机制建模是很多新手第一个翻车点。它的基本逻辑是:先给系统分配免费碳配额,实际排放量超过配额的部分,越超越多,单位碳价也越高,形成“价格阶梯”。复现时通常分三步来建。

第一步是计算免费配额。常见做法是按基准线法分配,例如给化石能源出力或外购电量乘以一个排放配额系数,得到总免费额度E_free。不同文献对配额系数的规定不太一样,有的按上网电量算,有的按机组出力算,你务必先读清楚论文的公式再编码。

第二步是计算实际碳排放量。典型排放源包括外购电力和天然气燃烧。外购电力的排放量等于购电量乘以电网排放因子;天然气燃烧排放量等于购气量(或燃气消耗量)乘以燃气排放因子。把每个时段的排放累加,就是当日总排放E_act。

第三步是把“碳排放超出量”映射成碳交易成本。设超出量为d = E_act − E_free,按下面的阶梯规则计费:

  • 第一段区间 [0, d1]:碳价为 c0
  • 第二段区间 (d1, d2]:碳价为 c1
  • 第三段区间 d > d2:碳价为 c2

其中 c1 > c0,c2 > c1,通常取c1 = α×c0,c2 = α²×c0,α大于1。碳交易成本不是d乘以一个固定价格,而是分段求解橙色的面积,类似于累计梯形面积。这里推荐一个非常好用的线性化技巧,不引入0-1变量,直接拆分碳排放量d:

d = d0 + u1 + u2

其中0 ≤ u1 ≤ d2 − d1,u2 ≥ 0,d0是不超过第一段的部分。那么碳交易成本为:

C_carbon = c0 × d0 + c1 × u1 + c2 × u2

直觉上的原理是:因为c2 > c1 > c0,最小化目标时计算机会自动先用低价额度覆盖碳排放量,不会出现“明明还有低价额度却用了高价额度”的不合理分配。这个技巧比用0-1变量+big-M法简洁得多,模型求解速度也快很多,是我复现时最愿意分享的写法之一。

3.3 两种机制在模型中的耦合方式

综合需求响应和碳交易不是两个并列的孤岛,它们最后都作用于调度策略。需求响应改变了用电用热曲线,进而影响机组出力分配和购能计划;碳交易成本进入目标函数,影响的是各机组的运行优先级。两者耦合后的效果是:碳价越高,系统越倾向于用清洁机组替代高排放机组;电价尖峰时,需求响应削减电负荷,减少对高排放边际机组的依赖,两者相互增强。

在代码结构上,我通常在目标函数里同时累加购电购气成本、设备运维成本、碳交易成本和需求响应补偿成本,让优化器一次性做全局权衡。约束则分成“物理设备约束”“能量平衡约束”“DR约束”“碳交易线性化约束”几组区块,每块单独写成一个变量Cons的拼接体,最后统一由optimize()求解。

4. 优化调度模型的目标函数与完整约束

4.1 目标函数怎么组成

综合能源系统优化调度论文的目标函数十有八九是“最小化系统日运行总成本”。我在复现时把它写成四个部分的和:

第一,购能成本,包括从电网购电的费用和从气网购气的费用。这一项直接驱动系统在“多用电”还是“多用气”之间做经济权衡。第二,设备运维成本,也就是CHP、锅炉、储能等设备的单位运行维护费用与出力相乘再累加,通常是一个线性项。第三,碳交易成本,这里用的就是前面阶梯碳机制算出来的C_carbon,它是平坦分段线性函数。第四,需求响应相关成本或补偿,有的论文会把需求响应刻画成给用户的补偿激励,也放进目标函数。

如果文献做的是双目标,比如“经济成本最小化+碳排放量最小化”,你可以在复现时用一个权重系数把两个目标线性加权,变成单目标求解,也可以直接保留双目标做Pareto前沿分析。但要注意,一旦引入双目标,Matlab侧要考虑权重扫描或改用多目标智能优化算法,而YALMIP+CPLEX就不是首选了。我在复现时发现,多数“综合需求响应+碳机制”的论文最终会把碳排放量直接货币化放进成本函数,所以单目标MILP完全够用,复现难度也更低。

4.2 约束清单与边界处理

约束体系我建议按下面的清单逐条检查,少一条模型可能不可行,多一条可能过约束:

  • 电功率平衡等式:每个时段,供给侧之和等于需求侧之和
  • 热功率平衡等式:每个时段,热供给侧之和等于热需求之和
  • 气功率平衡等式:购气量等于机组燃料消耗量
  • CHP机组出力上下限与爬坡约束
  • 燃气锅炉、电锅炉的出力上下限
  • 电储能充放电功率上下限、SOC上下限与始末能量相等约束
  • 热储能蓄放热功率上下限、储热容量上下限与周期始末状态一致
  • 需求响应后的负荷上下限(例如响应比例不超过基负荷的20%)
  • 碳交易机制的分段线性化约束

边界处理上有一个容易忽略的点:当需求响应把负荷往下压的时候,电平衡等式里“负荷”那一侧要统一替换成响应后的负荷值。新手常犯的错误是,一边在目标函数里算DR收益,一边平衡约束里还用原来的负荷曲线,这就是模型不可行的根源之一。我通常在代码最前面先算好P_load_DR和H_load_DR两个向量,之后平衡约束全部引用这两个向量,避免反复改错。

5. MATLAB实现全流程与关键代码

5.1 环境配置:YALMIP、求解器和数据准备

用Matlab复现这种优化调度模型,绕不开YALMIP这个建模工具箱。它可以把复杂的优化问题用很简洁的语句写出来,然后交给底层求解器求解。我目前的推荐组合是:Matlab + YALMIP + Gurobi(或CPLEX)。Gurobi和CPLEX都是商业求解器,求解MILP的速度和稳定性远超Matlab自带的intlinprog。如果你暂时拿不到Gurobi或CPLEX许可证,用intlinprog也能凑合跑,模型变量少于几千个时还能接受,但规模一大就会卡得难受。

安装时注意几点:YALMIP要把整个文件夹解压到纯英文路径中,然后在Matlab里用addpath(genpath('你的路径/yalmip-master'))添加。装好之后跑一下yalmiptest,确认YALMIP能正确识别求解器。Gurobi则要安装对应的数学优化版本,并在系统环境变量里允许它找到license文件,然后把gurobi的Matlab接口目录也加到路径里。求解器设置里,我习惯设置opts = sdpsettings('solver','gurobi','verbose',2),并顺手设置一个相对MIP gap,比如0.01或0.005,避免求解器追求过高的精度导致耗时爆炸。

5.2 核心代码结构与关键写法

我把核心代码骨架写在下面,这个结构可以直接套用到绝大多数同类论文的复现中。变量定义如下:

T = 24; % 决策变量 P_chp = sdpvar(1, T, 'full'); % CHP电出力 H_chp = sdpvar(1, T, 'full'); % CHP热出力 P_gb = sdpvar(1, T, 'full'); % 燃气锅炉耗气等效功率 H_gb = sdpvar(1, T, 'full'); % 燃气锅炉热出力 P_eb = sdpvar(1, T, 'full'); % 电锅炉耗电 H_eb = sdpvar(1, T, 'full'); % 电锅炉产热 P_buy = sdpvar(1, T, 'full'); % 购电 G_buy = sdpvar(1, T, 'full'); % 购气 P_ess_c = sdpvar(1, T, 'full'); % 电储能充电功率 P_ess_d = sdpvar(1, T, 'full'); % 电储能放电功率 SOC_ess = sdpvar(1, T+1, 'full'); % 电储能SOC H_ts_c = sdpvar(1, T, 'full'); % 热储能蓄热功率 H_ts_d = sdpvar(1, T, 'full'); % 热储能放热功率 S_ts = sdpvar(1, T+1, 'full'); % 热储能蓄热量 % 阶梯碳辅助变量 d_carbon = sdpvar(1, 1, 'full'); % 实际排放-配额 u1 = sdpvar(1, 1, 'full'); u2 = sdpvar(1, 1, 'full');

目标函数写法:

C_energy = sum(P_buy .* price_e + G_buy .* price_g); C_om = sum(P_chp .* om_chp + H_gb .* om_gb + P_ess_c .* om_ess + ...); C_carb = c0 * d_first + c1 * u1 + c2 * u2; C_dr = sum(DR_compensation); objective = C_energy + C_om + C_carb + C_dr;

约束写法我习惯全部并到一个变量里:

Cons = []; % 电平衡 Cons = [Cons, P_buy + P_wind + P_chp + P_ess_d == P_load_dr + P_eb + P_ess_c]; % 热平衡 Cons = [Cons, H_chp + H_gb + H_eb + H_ts_d == H_load_dr + H_ts_c]; % 气平衡 Cons = [Cons, G_buy == P_chp / eta_chp_gas + P_gb]; % 储能 Cons = [Cons, SOC_ess(2:T+1) == SOC_ess(1:T) + P_ess_c * eta_ess_c - P_ess_d / eta_ess_d]; Cons = [Cons, SOC_ess(1) == SOC_ess(T+1)]; ... % 阶梯碳约束 Cons = [Cons, E_act - E_free == d_first + u1 + u2]; Cons = [Cons, 0 <= u1 <= d2-d1, u2 >= 0];

最后统一调用:

ops = sdpsettings('solver','gurobi','verbose',1,'showprogress',1); result = optimize(Cons, objective, ops);

如果求解成功,可以通过value()提取所有变量的数值结果,用于后续绘图和分析。需要注意,YALMIP里变量命名中括号用错了或者维度不一致,都会直接报维度错误,这类问题我在第7节会详细讲。

5.3 求解与结果提取

求解完成之后,第一件事不是画图,而是检查求解状态。YALMIP的result.info会返回求解器的求解状态描述,比如提示“Problem successfully solved”。如果返回的是“Infeasible problem”,恭喜你,模型里八成有冲突约束。如果返回“Numerical trouble”,大概率是某处约束条件数值量级差得太大,例如几百kW的功率和0.01的效率系数放在同一个等式中,导致求解器数值条件恶化。

结果提取我习惯写成这样一个函数块:

P_chp_opt = value(P_chp); H_chp_opt = value(H_chp); P_buy_opt = value(P_buy); ...

提取之后再逐项核对物理量是否在合理范围内。比如购电量不应出现负值,储能充电和放电不应同时为正(其实如果目标函数设置合理,求解器不会让充放电同时为正,但有的论文里会特意加一个二值变量来强制互斥),热出力不能超过设备容量。这些检查虽然朴素,但能提前发现很多模型缺陷。

6. 复现结果怎么验证与分析

6.1 需求响应对负荷曲线的影响

模型跑通后,最直观的验证方式是对比“无需求响应”和“有需求响应”两种情况下的电负荷曲线。在分时电价的激励下,需求响应后的电负荷曲线应当出现明显的削峰填谷效果:高电价时段负荷下降,低电价时段负荷回升。我复现时设置的电价是峰平谷三段式,典型结果是峰时段电负荷削掉了大约8%到12%,谷时段负荷抬升了若干百分点,整体曲线变得更平缓。

这里要注意,很多论文里“削峰填谷”的数值是机制参数设定的结果,不是自然涌现的。尤其是弹性系数大小、负荷响应上下限、峰谷电价比,这三个参数直接决定DR效果的强弱。你在复现时如果发现削峰比例跟论文对不上,先检查这三个参数是否一致,而不是急着改代码。

6.2 阶梯碳机制的结果对比

碳交易模块的验证方法是设置三个场景:无碳交易、统一碳价、阶梯碳价。无碳交易时,系统会尽量多用电网电力和天然气,因为没有排放成本;统一碳价时,系统会降低高排放机组出力;阶梯碳价时,由于超过配额越多价格越贵,系统会更保守地控制排放,结果应当是碳排放量低于统一碳价场景。如果阶梯碳价场景下的碳排放量反而更高,那你就要检查阶梯碳成本函数是不是写反了方向。

从调度策略上看,阶梯碳机制会推动CHP机组从“电热产出比高”的模式向“多产热少发电”的模式调整,同时加大电锅炉、热储能的使用力度。碳价越高,这种替代效应越明显,直到碳价高到超过燃气设备的边际成本,系统才会大规模转向清洁能源。做曲线图时,我习惯把购电量、CHP发电量、燃气锅炉产热量、碳排放量四个量画在同一张图,对比不同碳价下的变化趋势,这样能很直观地看到机制的作用路径。

6.3 机制参数敏感性分析的做法

一篇合格的复现博文不能只跑出一组曲线就算完事。我建议至少做两个敏感性分析:一是变化碳价基准值c0,从低到高取五六个值,观察碳排放量和运行成本的变化趋势;二是变化需求响应弹性系数,观察对总成本的影响。通过这种参数扫描,你能验证模型行为是否符合经济学直觉:碳价升高,碳排量下降但运行成本上升;弹性系数增大,削峰效果增强,用户用能成本会发生变化。

参数扫描的代码实现很简单,把c0和弹性系数写成外部变量,套一层for循环反复求解即可。但要注意,每次求解之间要重新初始化YALMIP的变量和约束,或者把整个建模过程封装在一个函数里调用,否则变量名冲突会让你怀疑人生。我把这部分代码直接做成了脚本,每轮循环内调用一次自定义函数,跑完自动收集结果并画图,效率很高。

7. 复现过程中的常见问题与避坑指南

7.1 模型求解阶段的大坑

求解阶段最常见的报错是模型不可行。遇到这个问题,我建议根据经验逐段排查。第一步检查储能SOC约束:SOC变量维度是T+1,但充放电变量只有T个,等式左侧要写SOC_ess(2:T+1),右侧写SOC_ess(1:T),位置错一个就会让第一个和最后一个约束打架。第二步检查平衡等式:把所有参数名打出来,核对设备效率是否已经折算到对应侧。第三步检查阶梯碳约束:注意d_first、u1、u2这三个辅助变量都必须是sdpvar类型,不能是double类型,否则YALMIP会直接报类型错误或产生非线性项。

另一个很有迷惑性的坑是:目标函数里如果出现负数权重,求解器可能在没有约束限制的地方把决策变量推到负无穷。比如储能运维成本按放电量计费,但如果充电时你没加约束,求解器可能让储能无限“充电再放电”来套利,数字上是等式的特解,物理上却完全不对。解决办法是给充放电功率加非负约束,并限制SOC在合理范围,必要时用二值变量强制充放电互斥。

7.2 代码与数据处理的隐蔽问题

第一个数据处理的坑是维度不匹配。电价、气价、负荷、风光预测四条曲线的长度必须是T,但新手经常从Excel读进来时多了一个表头行,导致price_e是24×1而load是1×24,YALMIP虽然不报错,但后面所有等式全部错位。建议在读入数据后立刻做一次维度检查。

第二个坑是YALMIP内置函数的使用习惯。很多初学者会在约束里写min()、max()、abs(),YALMIP会把它自动转化为带额外辅助变量的表达式,这本身没问题,但如果你在用旧版本YALMIP,部分非线性函数会生成非凸约束,导致求解器报“Non-convex quadratic”之类的错误。更稳妥的做法是在建模前就用线性式子把这类函数拆开,例如用上一节讲的阶梯碳线性化写法,而不是依赖YALMIP的自动展开。

第三个坑经常出现在复现他人代码时:一些博文或论文给的代码里用了“矩阵整体约束”,也就是把多个时段用一个向量等式表达,而实际模型某些约束却需要逐时段写。两种写法混合使用很容易出错。我的建议是全模型统一用向量写法,用点乘运算符处理变量与系数相乘,避免出现“变量列向量乘系数行向量”导致维度爆炸的问题。

7.3 复现文献的通用方法论建议

最后分享一点我自己的流程习惯。拿到一篇综合能源优化调度论文,我不会一开始就写代码。第一步先把论文里的系统拓扑图抄下来,把每个设备的输入、输出、效率、约束边界做成一个表格。第二步把论文里的目标函数和所有约束用纸面公式重写一遍,标清楚哪条是等式、哪条是不等式、哪些变量是连续变量、哪些是整数变量。第三步才打开Matlab,先建最简场景(比如只含购电+CHP+电负荷),跑通后再逐步加设备、加DR、加碳机制。

这样做的好处是能快速定位问题。模型变大后,如果求解结果不对,你可以逐层剥离机制模块,对比“无碳交易”“有碳交易”“有DR又有碳交易”各种场景的结果差异,判断是哪个模块写歪了。我在复现过程中发现,绝大多数代码翻车都不是求解器问题,而是模型公式没有逐条核对清楚。数据、公式、代码三者的映射关系梳理清楚了,算法本身反而不容易出大问题。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询