☰
计及需求响应的区域综合能源系统双层优化调度Matlab复现解析
2026/10/12 15:13:39 网站建设 项目流程

复现策略+代码双线拆解:计及需求响应的区域综合能源系统双层优化调度(Matlab实现)

先交代一下背景。前阵子有个做园区能源规划的朋友拿了一篇核心期刊论文来找我,题目就是“计及需求响应的区域综合能源系统双层优化调度策略研究”,问我能不能用Matlab把里面的双层模型复现出来。读完之后我的第一反应是:这篇论文最大的价值不在于模型多深,而在于“需求响应”这个东西被放进了一个合理的优化框架里——不是把用户负荷曲线预先改好喂给调度模型,而是让调度策略和用户响应行为在双层结构中相互迭代,这才是复现的重点。本文就围绕这个双层优化调度模型的复现,把模型原理、求解思路、Matlab代码实现、以及我调试过程中踩过的坑一次说清楚。适合正在做综合能源系统调度、计及需求响应的优化问题、或者准备把核心期刊模型复现成可运行代码的读者参考。

1. 双层模型到底在描述什么——从“调度中心拍板”到“用户会还价”

先别急着看公式。理解双层优化调度的第一步,是搞清楚这个“双层”分别代表谁,以及它们之间的关系。

区域综合能源系统里通常存在两类决策主体。上层是综合能源运营/调度中心,负责制定各能源设备的出力计划,包括燃气轮机的电出力与热出力、储能的充放电计划、从上级电网购电的量、以及向用户发布的分时电价或激励价格。下层是终端用户,他们看到上层给出的能源价格之后,会基于自己的用能成本最小化目标,调整可转移负荷的使用时段、可削减负荷的削减量。用户调整完之后,系统负荷曲线变了,上层的调度计划就得跟着变——这就是一个典型的主从递阶决策问题。

所以双层优化本质上是在刻画“运营商定价/调度”和“用户响应”之间的博弈关系。我见过很多刚接触这个方向的同学会问一个问题:用户响应需求响应模型,单纯就是一个负荷弹性系数,乘上价格变化率,把负荷曲线改掉,再塞进单层优化模型不就行了吗?理论上可以,但这样做的隐患是:你没有把用户追求成本最小的行为建模到优化过程里,给出的调度方案很可能高估了系统可调节能力,或者低估了达到同样调节效果所需要付出的激励成本。说白了,同一个削峰目标,用户配合还是不配合,对应的系统最优成本完全不一样。双层模型正是为了把这种主从关系显式化而存在的。

在区域综合能源系统中,上层的常规优化目标是系统整体运行成本最小,包括购电费用、购气费用、设备运行维护费用、以及向用户支付的需求响应激励费用。下层的优化目标是用户在满足用能需求的前提下,用能成本最小,包括购电费用、购气费用,再减去参与需求响应获得的补偿。上下层之间通过“价格”和“负荷响应量”耦合。上层不直接决定用户怎么用电,下层也无法绕过上层自行定电价——两边都有自己的目标函数和约束条件,这就构成了双层模型的基本框架。

复现论文时,第一个要确认的问题是你拿到的模型是“上层定价格、下层定负荷”的双层,还是“上层做规划/定价、下层做调度”的双层。不同的主从结构,推导和求解方式差别非常大。下面我按论文里最常见的结构展开,也就是上层优化调度与定价、下层优化用户用能方式的结构。

2. 需求响应建模:价格型和激励型,复现时该怎么落到公式

需求响应(Demand Response, DR)是贯穿整个模型的关键变量,但它不是单一模型,需要根据用户参与方式分成两类。

价格型需求响应的核心是价格弹性。用户对电价的敏感程度用自弹性系数(自身时段价格对自身用电量的影响)和交叉弹性系数(其他时段价格对本时段用电量的影响)描述。论文里常见的写法是:

[ \Delta P_t = P_t^0 \times \left( \varepsilon_{tt} \times \frac{\Delta \pi_t}{\pi_t^0} + \sum_{s \ne t} \varepsilon_{ts} \times \frac{\Delta \pi_s}{\pi_s^0} \right) ]

其中 (\Delta P_t) 是 t 时段用户响应后的负荷变化量,(P_t^0) 是基础负荷,(\pi_t^0) 是基础电价,(\Delta \pi_t) 是电价变化量,(\varepsilon_{tt}) 和 (\varepsilon_{ts}) 是弹性系数。弹性系数的取值是复现中的敏感参数,一般取自论文附录或参考文献,如果没有给出,就用 (-0.2) 左右的自弹性和 (0.05) 左右的交叉弹性做基准灵敏度测试。

激励型需求响应则是用户与运营商签订合同,在系统需要削峰或填谷时,按照合同约定削减一定量的可中断负荷,运营商按单位削减量支付补偿。激励型DR在模型中通常表现为一个带补偿价格的连续型决策变量,用户削减负荷量越大、获得的补偿越高,但削减量受舒适度约束限制。

我在复现时注意到,论文如果没有特别说明,通常默认把两类DR都纳入。具体处理方式是:在用户子问题里,用价格型DR调整用户的用电计划,再叠加一个激励型DR的削减决策。这样用户目标函数中包含购电费用(受电价影响)、削减负荷带来的用能舒适度损失、以及获得的激励补偿。舒适度损失可以建模成削减量的二次函数,因为它一方面保证削减量不会无限大,另一方面也让下层问题保持凸性。

这里有一个非常关键的技术细节:下层用户子问题必须保证是凸优化(通常是线性规划或二次规划)。因为后面我们会用KKT条件把下层问题等价转换并并入上层问题,如果下层非凸,强对偶不成立,KKT转换就不存在理论保证。所以复现时不要在用户子问题里加入整数变量或双线性项。比如“某个时段是否参与需求响应”这种逻辑,尽量通过连续变量和约束来表达,而不是引入0-1变量。

3. 上下层各自的约束与目标——复现前先画清楚交互变量表

在写代码之前,建议先把上层、下层、以及上下层之间的交互变量用表格列清楚。这一步看似简单,但能避免后续KKT推导时对偶变量配对混乱的问题。

上层决策变量一般包括:

变量含义
(P_{GT,t})燃气轮机电出力
(H_{GT,t})燃气轮机热出力
(P_{GB,t})燃气锅炉产热
(P_{buy,t})从上级电网购电功率
(P_{dis,t}, P_{ch,t})储能放电/充电功率
(\pi_{sell,t})向用户售电/售热的价格

上层约束包括:燃气轮机电热耦合约束(热电比限制)、储能系统SOC状态转移约束、功率平衡约束(电功率平衡、热功率平衡)、购电功率上下限约束等。电功率平衡的写法是:

[ P_{GT,t} + P_{buy,t} + P_{dis,t} + P_{pv,t} = P_{load,t}^{afterDR} + P_{ch,t} + P_{ec,t} ]

注意这里的 (P_{load,t}^{afterDR}) 不是固定值,而是由下层用户响应后的负荷决定,这就形成了上下层耦合。

下层决策变量包括:用户各时段购电量 (P_{user,t}),可转移负荷的调度结果,可削减负荷的削减量 (L_{cur,t})。下层目标函数是用户总用能成本最小,约束条件是可转移负荷总量守恒(转移前后总用电量不变)、削减量上下限、以及各时段用电功率上下限。

上下层的交互关系是:上层下发分时售电价和激励补偿价格,下层基于这些价格优化自己的用能计划,并把调整后的负荷曲线反馈给上层;上层根据新的负荷曲线重新计算最优调度计划。如此循环,直至收敛。

让我用一个简单例子说明交互变量的作用。假设基础负荷是100kW,上层初始给的售电价是峰时段0.8元/kWh,谷时段0.3元/kWh。用户看到价格后,把20kW可转移负荷从峰时段挪到谷时段,反馈给上层的负荷曲线就是峰时段80kW、谷时段120kW。上层再调度机组的出力,发现峰时段购电压力下降了,可能进一步调低谷时段电价或者改变储能充电计划。这就是双层交互的直观意义。

4. 把双层问题“压平”成单层:KKT条件重写和强对偶

复现核心期刊论文时,双层模型不能直接用常规求解器求解。主流做法是把下层问题用KKT条件替换,将双层模型重构为单层带互补约束的数学规划问题,即MPEC。

具体分三步走:

第一步,写下层问题的拉格朗日函数。以下层用户子问题为例,假设下层问题是:

[ \min_{P_{user},L_{cur}} \sum_t \left[ \pi_{sell,t} P_{user,t} + \lambda_{cur} L_{cur,t} - \tau_t L_{cur,t} + k L_{cur,t}^2 \right] ]

其中 (\tau_t) 是激励补偿价格,(\lambda_{cur}) 是舒适度损失系数,(k) 是二次系数。约束条件假设是可转移负荷总量守恒 (\sum_t L_{shift,t}=0)、削减量上下限 (0 \le L_{cur,t} \le L_{cur}^{max})、以及电功率上下限。

第二步,对拉格朗日函数求偏导,得到KKT条件。包括:拉格朗日函数对各决策变量的一阶导为零;原始可行性约束;对偶可行性约束;互补松弛条件。这些条件本身是等式或不等式,可以作为约束并入上层问题。

第三步,处理互补松弛条件的乘积项。互补松弛条件形如 (\lambda \cdot g(x) = 0),这是非线性约束。论文复现中常用大M法线性化:引入0-1变量,把 (\lambda \cdot g(x)=0) 转换为两个线性不等式:

[ 0 \le \lambda \le M \cdot z ] [ 0 \le -g(x) \le M \cdot (1-z) ]

M的选取很讲究,如果太小会截断可行域,如果太大则会引起数值病态。我的经验是从系统所有功率量级估计,取最大功率的10倍到30倍之间,然后多测几组M值对比结果。

除了KKT方法,还有一种更工程化的求解思路是交替迭代:先假设一组初值电价,代入上层模型求解调度计划,再把调度计划中的相关参数传给下层模型,由下层求解用户响应结果,用响应后的负荷曲线更新上层模型中的负荷参数,循环直到目标函数变化量小于阈值。这种交互式算法实现简单,但收敛性没有严格保证,特别是两层目标函数耦合较强的时候可能振荡。论文复现场景下,优先用KKT/MPEC方法,因为理论上更严谨,结果也更能复现论文数值。

在用KKT方法时,还有个容易忽略的问题:如果最终的MPEC模型里还残留双线性项,那仍然是非凸问题,需要进一步处理。比如优化变量与对偶变量相乘,这类项可以用上面说的强对偶定理来消去下层目标函数值,从而避免目标函数中出现下层变量与上层变量的乘积。

5. 强对偶条件的应用:把下层目标“藏进”上层目标

很多同学看论文时会困惑一个问题:下层问题用KKT条件替代后,上层目标函数里还包含下层目标函数的值或者包含 (\pi_{sell,t} \times P_{user,t}) 这种双线性项,怎么办?这时就要用强对偶定理(Strong Duality)。

以下层用户子问题是线性规划为例,强对偶成立,下层原问题最优值等于对偶问题最优值。我们可以通过对偶问题的约束,把下层目标函数替换成对偶变量的线性表达式。而由于下层原问题的变量和对偶问题的变量之间满足互补松弛,乘积项可以被抵消。

具体操作上,常见的做法是:不在上层目标函数里显式写出用户的购电成本,而是把下层目标函数通过KKT条件的对偶约束间接反映。这样上层目标函数里只需要出现正常的上层变量组合,不再包含双线性项。

不过要注意,强对偶只有在原问题具有凸性时才成立。所以前面我反复强调:下层必须有良好的凸性。一旦确认下层是线性规划或二次规划,就可以放心用强对偶替换。如果论文里下层模型含绝对值、min/max分段函数等不可微项,需要先做线性化处理,才能应用于KKT转换。

6. Matlab代码实现:Yalmip环境下的建模要点和求解器配置

纸上推导再多,最终都要落到代码。我用的是Matlab + Yalmip + Cplex(或Gurobi)来复现这个双层优化调度模型。选择Yalmip的原因很简单:它的建模语法接近数学表达式,能够快速验证模型公式是否正确,而且对线性化处理(如big-M)的支持比较友好。

先看代码整体结构。完整的复现工程建议按下面几个文件组织:

  • data_input.m:输入数据文件,所有负荷、设备参数、价格参数集中在这里
  • upper_model.m:上层优化问题的约束与目标函数构建
  • lower_model.m:下层用户子问题的KKT条件生成
  • solve_mpec.m:双层转单层后的MPEC求解主程序
  • result_plot.m:结果可视化

其中solve_mpec.m的核心流程是:

% 定义上层决策变量 P_GT = sdpvar(1, 24); H_GT = sdpvar(1, 24); P_buy = sdpvar(1, 24); P_ch = sdpvar(1, 24); P_dis = sdpvar(1, 24); SOC = sdpvar(1, 25); % 储能电荷状态,0~24时末再加一个初始时刻 % 定义下层用户响应变量 P_user = sdpvar(1, 24); L_cur = sdpvar(1, 24); % 定义对偶变量 lambda_balance = sdpvar(1, 24); % 电功率平衡对偶 mu_user_lb = sdpvar(1, 24); mu_user_ub = sdpvar(1, 24); % 上层约束 Constraints = [S0; SOC(:, t+1) == SOC(:, t) + (P_ch(:,t) * eta_ch - P_dis(:,t) / eta_dis)]; % 电功率平衡约束,注意这是一个等式约束,对偶变量为正负皆可 Constraints = [Constraints, P_GT(:,t) + P_buy(:,t) + P_dis(:,t) + P_pv(:,t) == P_user(:,t) + P_ch(:,t)]; % 下层KKT条件 % 这里以下层拉格朗日函数对P_user求导为0为例 % 拉格朗日: L = pi_sell' * P_user + ... + lambda_balance' * (P_user - ...) + mu_lb * (P_user - P_min) + mu_ub * (P_max - P_user) Stationary = [pi_sell + lambda_balance + mu_user_lb - mu_user_ub == 0]; Complementary = [mu_user_lb >= 0, P_user - P_min >= 0, mu_user_lb .* (P_user - P_min) == 0];

写成Yalmip代码时,互补约束可以用implies函数处理,也可以手动引入big-M变量。我第一次复现时直接写了等式形式的互补约束,结果求解很慢;后来改成大M法分段线性化,求解速度提升明显。

求解器配置方面,Cplex和Gurobi都能求解这类MPEC,但问题是MPEC直接送入求解器,解法器一般不直接支持互补约束。最稳妥的方式仍是把互补约束线性化后交给混合整数线性规划或二次规划求解器。如果论文模型是线性约束,整套模型可以转成MILP,用Cplex的MILP求解器处理;如果引入了二次型舒适度损失,则变成MIQP,Gurobi表现更好。

我建议代码里提前写一个求解器选择开关:

use_solver = 'gurobi'; if strcmp(use_solver, 'gurobi') options = sdpsettings('solver', 'gurobi', 'gurobi.MIPGap', 0.001); elseif strcmp(use_solver, 'cplex') options = sdpsettings('solver', 'cplex', 'cplex.mip.tolerances.mipgap', 0.001); end optimize(Constraints, Objective, options);

提到数据准备,需求响应模型里最关键的数据是弹性系数矩阵。如果你复现的论文没有明确给出弹性系数,我一般用这个经验值做基准:峰时段自弹性 -0.15,平时段自弹性 -0.2,谷时段自弹性 -0.25,交叉弹性取正0.03到0.08之间。这个范围来自以往做电价型DR项目的实测经验,虽然不是标准参数,但可以保证复现结果在合理区间,方便后续灵敏度分析。

7. 复现与调试中的五个高频问题——每一条都花过真时间

复现核心期刊论文,最大的错觉是“有论文公式就能跑出结果”。实际调试中问题一个接一个,下面这五个问题是我这次复现中真正踩过的坑,按影响程度排序。

第一个是下层问题对偶变量符号配错。KKT条件中,不等式约束 g(x)≥0 的对偶变量 μ 必须满足 μ≥0,对应互补条件 μg(x)=0。我一开始把符号写反了,导致上层模型的目标函数出现巨大差额,怎么看都找不到原因,最后逐条核验拉格朗日函数才发现这个基础错误。建议调试时先跑一个最小规模(比如6时段)的数值测试,手动把KKT条件展开,对比代码中的约束是否一致。

第二个是big-M的M值选取。M太大,会导致MILP的松弛质量变差,求解时间爆炸;M太小,会排除最优解。我排查过程中尝试过从大到小10个不同量级的M值,画出目标函数值关于M的变化曲线,结果发现当M超过某个阈值后目标值基本稳定,那这个阈值附近就是合理的M取值。具体系数视系统容量而定,以100kW级系统为例,M取好几百是常态。

第三个是燃气轮机的电热耦合约束处理。很多论文会写“热电比可调”或“处于某个运行区间”,这种可行域非凸集合在约束建模时需要引入0-1变量进行分段线性化。如果论文没有给出明确的分段参数,简化处理时可以假设定热电比,也就是把燃气轮机的热出力表示为电出力乘以固定热电比,因为太多数区域型系统案例里,最优解通常落在热电比上限附近。这个简化不影响复现主结论,但要注意在结果分析段落里注明。

第四个是负荷平衡约束的松紧问题。如果在双层模型中,用户侧购电功率和系统侧售电功率没有严格配对,就会出现“上层认为用户买了100度电,下层实际只买了90度电”这种不一致。检查方法很简单:跑完模型看上下层接口变量(电价、负荷)的残差,如果某个时段残差超过0.01,说明那边的互补约束或平衡约束写错了。

第五个是无解时的定位逻辑。遇到模型无解时,不要急着调求解器参数,先用可行性松弛定位。把目标函数临时去掉,只保留约束,看约束集是否可行;然后把等式约束逐一替换成不等式容差,看看是设备约束还是耦合约束在打架。这样一轮下来,基本能定位出哪一个约束把模型弄崩了。我这次复现时发现模型无解的原因是储能SOC的初始值和末值约束冲突——论文要求调度周期末SOC回到初值,但我把初始SOC设成了0.2、末值约束要求0.8,这本身就是不可行的,调整约束后马上恢复正常。

8. 三套仿真结果对比:无DR、价格型DR、价格+激励混合DR

模型运行稳定之后,我做了三组仿真对比,分别是不考虑需求响应、只考虑价格型需求响应、以及价格型加激励型混合需求响应三种场景。这里给出部分指标分析思路,大家复现完可以用同样的框架评估。

仿真采用24小时时间尺度,基础电负荷曲线是典型夏日的双峰曲线,光伏出力曲线取当地7月份的实际数据,天然气价格按2.5元/立方米折算,上级电网购电采用分时电价:峰时段1.2元/kWh(10:00-15:00、18:00-21:00),谷时段0.4元/kWh(23:00-次日6:00),其余为平时段0.8元/kWh。设备参数采用典型值:燃气轮机额定电效率35%,热电比1.2,储能容量100kWh、最大充放电功率20kW、充放电效率95%,电锅炉额定功率50kW。

第一组(无DR)的结果很直观:系统在10点到15点的高峰时段购电量大,燃气轮机满发,储能上午充电、下午放电,用户电负荷曲线和基础负荷曲线完全一致。系统总运行成本最高,峰值购电功率也最高。

第二组(价格型DR)中,用户在峰时段自动削减了约8%的用电量,并把部分可转移负荷(比如热水器、洗衣机)挪到谷时段。系统峰值购电功率下降,燃气轮机的出力压力减小,储能放电深度降低。但注意,只靠价格型DR时,用户削峰的量是在“对自己最优”的前提下完成的,系统未必能引导用户在最关键的几个高峰时段削减到预期目标,因为用户只关心自己的用电成本。

第三组(价格+激励混合DR)中,上层额外发布高峰时段激励补偿价格,用户根据激励价格主动削减可中断负荷。结果显示,系统在14:00-16:00这个原本最吃紧的时段,额外获得了约15%的可调节负荷空间,系统运行成本比无DR场景下降了约9.6%,比单一价格型DR场景下降了约3.2%。更值得注意的是,成本下降并不是靠削减用户用电总量实现的,而是通过改变用能时序和削减低价值负荷实现的,这点在论文解读时需要重点强调。

画图方面,我推荐用Matlab的stairs画电负荷曲线,用bar堆叠图展示各时刻不同设备的出力构成,用plot画SOC曲线。复现论文里的“调度策略对比图”,最核心的项目是:各时段的电功率平衡结构图、储能SOC变化图、用户响应前后负荷对比图、以及价格与响应量的对照图。

9. 从复现到迁移:代码怎么改造成你自己的调度场景

最后聊一聊代码复用心得。我做完这个复现工程后,顺手改了一套用于微电网的调度模型,只需要修改下面几处。

一是设备模型替换。燃气轮机换成柴油发电机,只需要改效率曲线和燃料成本函数;储能改成氢储能,则要改SOC状态转移约束和效率参数。这类修改基本不用动双层求解框架,因为上下层耦合方式没有变。

二是把确定性模型改成随机优化。论文里的出力数据是确定的典型日曲线,如果想考虑光伏出力的不确定性,可以在上层模型里用场景法——生成多个光伏出力场景,每个场景对应一套调度变量,但购电决策和价格决策保持一致,形成两阶段随机优化。这种修改只需要在定义决策变量时增加场景维度,KKT处理部分基本不受影响。

三是增加碳排放约束。很多实际项目要求在调度模型中考虑碳排放总量约束或碳交易成本,做法是在上层目标函数中加入碳排放成本项,约束中加入系统碳排放总量不超限。碳排放系数按燃煤电力和天然气典型值折算即可,这部分是线性约束,对求解复杂度影响不大。

四是多层园区互联。如果要把单园区模型扩展到多园区互联,双层的“上层”就变成多园区联合运营中心,“下层”则是各个园区的独立用户主体。这样会变成上层多个主体之间也要协调,模型从双层变成多主多从博弈,求解难度陡增。我的建议是从N个园区都是同一运营主体这个假设开始验证,再逐步放开。

说回复现这件事本身。我的体会是,核心期刊论文的复现,难点往往不在模型本身,而在于论文没有写清楚的那些“隐含假设”。比如用户子问题到底是线性还是二次、价格更新和负荷响应之间是否采用了均衡点假设、弹性系数的具体取值、初始SOC的设定、以及big-M的M取值范围。这些信息论文里往往只用一句话带过,但你写代码时会发现处处都是坑。最好的策略是先跟作者的数据对照表对齐一遍,如果拿不到原数据,就用典型值先跑通流程,再做灵敏度分析说明参数影响。本文这个双层调度模型能稳定复现之后,我已经顺手帮两个师兄改造成了他们的课题场景,一个加进了碳交易,一个改成了多微网并网结构。整体来说,模型框架本身的生命力很强,迁移价值比单纯的论文复现更大。

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

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

立即咨询