最近把一篇EI收录的“考虑区域多能源系统集群协同优化的联合需求侧响应模型”论文用Matlab从头复现了一遍。这类工作现在很常见,但要把论文里的数学公式转成能跑的代码,中间隔着的不是一行代码,而是对模型、数据、求解器的一整套理解。这篇文章我思前想后,决定抛开论文原文,纯粹从复现和工程落地的角度,把“区域多能源系统集群”到底怎么建模、“联合需求侧响应”怎么嵌入优化、“Matlab怎么实现”这三件事讲清楚。如果你正在做综合能源调度、需求响应或者集群协同优化方向,刚好卡在从公式到代码这一步,这篇文章应该能帮你省不少时间。
1. 项目概念拆解:从标题里能读出哪些信息
1.1 区域多能源系统集群是什么
“区域多能源系统集群”这个词可以拆成三层:区域、多能源系统、集群。区域指的不是单个建筑或者单个微网,而是一个具有一定空间尺度的范围,比如一个工业开发区、一个大型社区、一个城市片区。多能源系统指的是电、气、热、冷等多种能量形式在同一个系统内耦合,往往有热电联产机组、燃气锅炉、电锅炉、储能、光伏、风电这些设备。集群则强调多个这样的子系统通过电力联络线或者热力管道连接在一起,彼此之间可以交换功率和能量。
我复现时采用了一个三集群互联结构,每个集群内部都有独立的负荷、分布式电源和储能,集群之间由联络线相连。这种结构在EI论文里很常见,因为“集群协同优化”的核心目的,就是让每个集群在满足自身供需平衡的同时,借助其他集群的富余容量来降低系统整体的运行成本。所以标题里的“集群”绝对不只是空间概念,而是在算法上必须体现出可互济、可协调的交互关系。换句话说,如果你拿到代码后只是把三个孤立的系统分别优化,再拼起来当“集群”,那模型从一开始就错了。
1.2 “协同优化”与“联合需求侧响应”分别解决什么问题
“协同优化”解决的是多主体博弈和资源共享问题。如果每个集群只盯着自己最小化成本,不考虑互联功率,结果很可能出现某个集群缺电、另一个集群富余,整体既不经济也不安全。协同优化就是把这些集群放在一个优化框架里,统一求解出每个集群的运行计划以及集群间的交互功率。很多论文采用集中式优化,也就是所有数据汇集到中心调度层,直接求解全局最优;但也有不少论文为了保护集群隐私,用分布式算法,比如交替方向乘子法(ADMM)或目标级联分析法,让集群之间通过交换边界信息迭代逼近全局最优。我这里两种方案都做了对比,后面会细说。
“联合需求侧响应”则是在负荷侧做文章。传统需求侧响应只针对电负荷,比如削峰填谷、可转移负荷、可削减负荷。联合需求侧响应把电负荷和热负荷(有时候还有冷负荷)放到同一个响应机制里。因为多能源系统里电和热是强耦合的,比如热电联产机组在发电的同时产热,如果只削电不削热,会导致机组的热出力被迫调整,甚至造成能量浪费。所以联合响应要考虑用户对电能和热能的可调节潜力,用价格信号或者激励信号引导用户调整用能行为,同时让配电网和热力系统都受益。
1.3 这个复现项目适合谁参考
这个项目天然适合三类人。第一类是目前在做综合能源系统优化调度相关课题的研究生,尤其是需要复现EI/SCI论文、或者准备写新论文的,这套模型无论从创新点还是公式推导上看都是一个很好的起点。第二类是去电力设计院、售电公司或者综合能源服务公司做系统规划的工程师,他们需要把类似的调度模型嵌入到实际系统中,验证集群互济的收益。第三类是有Matlab编程基础但刚接触优化建模的人,因为整个项目用Yalmip工具箱写优化问题,门槛不高,看懂之后可以快速迁移到别的模型。
从我个人的复现经历来看,这类项目的难点从来不是Yalmip语法,而是“怎么把论文里大段大段的公式拆成可计算的目标函数和约束矩阵”。所以下面我把模型构建和Matlab实现的细节展开讲。
2. 核心模型构建思路
2.1 集群协同优化的总体框架
在复现一个优化调度模型前,第一步不是写代码,而是把物理系统映射成数学语言。我的总体框架分三层:
第一层是设备层,每个集群内部有热电联产机组、燃气锅炉、电储能、光伏、风电机组。它们的出力特性用运行区间、爬坡速率、能量转换效率来描述。比如热电联产机组就是一个典型的耦合单元,它的电出力与热出力之间存在一个热电比约束,不能像普通机组一样独立调节。这个“耦合”是多能源系统建模最核心的东西,也是区别于纯电力系统优化的关键。
第二层是网络层,描述集群之间的交互功率。这里很重要的一点是,交互功率是一个带符号的变量,从集群i流向集群j记为正值,反方向就为负值。加上联络线容量约束和功率平衡约束,才真正体现出“集群协同”。实际代码里,每个集群的功率平衡约束都会出现一个名为P_exchange的变量,中心协调层不断更新它的参考值,直到各集群对它达成一致。
第三层是市场/需求层,也就是联合需求侧响应的部分。用户可以根据分时电价和热价调整负荷曲线,这个反应可以用价格弹性系数来描述,也可以直接引入可转移负荷变量。我采用的是“基础负荷+可转移负荷+可削减负荷”的建模方式,这也是论文里最常见的一种。
整体优化目标一般写成系统总运行成本最小,包含从电网购电费用、购买天然气费用、设备运维费用,以及向用户支付的需求响应补贴。为了保证算法收敛稳定,还可以在目标里加上一个关于交互功率与参考值偏差的惩罚项,这个要视最终选择的求解方法而定。
2.2 联合需求侧响应的典型建模方式
联合需求侧响应听起来很高深,落到数学上其实就三类手段:价格弹性、可转移负荷、可削减负荷。
价格弹性是宏观的,用弹性矩阵E把电价的相对变化映射到负荷的相对变化。比如峰时电价升高,用户自然会把一些可调节负荷挪到谷时。对于电负荷,可以设一个24×24的弹性矩阵,对角线是自弹性(负值),非对角线是交叉弹性。热负荷也可以用类似的方式,只不过激励价格换成了热价或者环境温度补偿。实际中弹性系数很难精确获取,所以很多论文只是把它作为一个算例参数,重点观察不同弹性水平下系统运行方式的变化。
可转移负荷是比较微观的建模方式。每一类负荷(比如洗衣机、充电桩、蓄热电采暖)有一个总用电量,运行时间窗口可以平移,但总耗电量不变。在24小时模型里,我通常用整数变量表示转移后的开始时间,或者用连续变量把功率分配到不同时段。论文为了可解性,往往会松弛成连续变量。简单来说,就是把“这个设备必须在一个时段内开满功率”松弛成“这个设备的总耗电量在一天内固定,具体每时段用多少可以由模型决定”。
可削减负荷则是允许在特定时段中断或者削减一部分用电/用热功率,但削减总量不能超过用户约定比例,并且削减行为要支付补贴。把这三类响应统一放进约束里,目标函数的负荷侧成本项也随之变化。
需要注意的是,很多新手会把需求响应当成一个固定参数的外生变量,直接把负荷曲线改小,这种做法就失去了“响应”的含义。正确的做法是把负荷曲线作为优化变量的一部分,由模型根据电价和热价内生决定。这样才能观察协同优化和需求响应之间的耦合效应。
2.3 目标函数与约束条件的展开逻辑
我用一个24时段、3个集群的算例来展开。目标函数写为:
$$\min \sum_{t}\sum_{i}\left[ C_e(t)P_{\text{grid}}(i,t) + C_g(t)V_{\text{gas}}(i,t) + \sum_k C_{\text{op}}(k)P_k(i,t) + C_{\text{dr}}(i,t) \right]$$
其中C_e是外购电价,P_grid是集群从电网取电功率,注意买电和卖电可以分别建模也可以统一用一个有符号变量;C_g是天然气价格;V_gas是消耗的天然气量;C_op是设备运维成本;P_k是设备出力;C_dr是需求响应补贴成本。
约束条件我分成五组:
电功率平衡:集群内部发电加从电网购电加交互功率输入减存储充电等于电负荷减可转移电出力减削减量。
热功率平衡:CHP产热加锅炉产热加热储能放热等于热负荷减可削减热负荷。
设备运行约束:各机组出力上下限、爬坡、储能SOC递推。
交互功率约束:联络线容量限制、交互功率对称约束,即i到j的功率等于负的j到i的功率。
需求侧响应约束:可转移负荷总能量不变、削减比例上限、响应前后用户满意度约束。
这里面最容易被忽略的是储能SOC的时序约束。差分方程是每个时段都有的耦合约束,如果写成Yalmip约束时要循环24次;还有初始SOC和末尾SOC要闭环,否则储能会把电量全部放光。我第一次复现时在这里卡了很久,后面在常见问题里会讲到。
3. Matlab实现实操要点
3.1 整体代码结构设计
我建议不要把所有的代码堆在一个m文件里,否则迭代到后面根本调不动。我采用的目录结构是这样的:
main.m:主程序,负责参数设置,调用建模函数,启动求解,输出结果。
data/input_data.xlsx:存所有基础数据,包括分时电价、气价、负荷曲线、设备参数、联络线参数。
model/build_system_model.m:把设备参数转换成优化变量和约束,返回约束组。
model/build_dr_model.m:构建需求侧响应相关的变量和约束。
solver/run_admm.m:如果采用分布式求解,这里放ADMM迭代循环;集中式的话直接调Yalmip。
result/plot_results.m:画图,输出表格。
这种结构的好处是,当你需要调某个集群的设备参数时,只需要改Excel,不用改代码。实际工程中,参数变动非常频繁,如果是写死在代码里,每改一个参数再跑一遍,时间全浪费了。另外,每个函数文件里我都加了详细的注释,说明这个函数的输入输出是什么、变量维度是多少,调起来省心得多。
3.2 数据准备与参数初始化
我用的算例是三个集群,每个集群都是24小时,时间分辨率为1小时。数据包括:集群1有CHP、光伏、电储能;集群2有风电、燃气锅炉、热储能;集群3有电锅炉、CHP,且三个集群通过两条联络线互联。所有设备都有容量约束和效率参数。这里为了说明方便,我给出其中一组典型的数值:
分时电价:峰时1.2元/kWh,平时0.8元/kWh,谷时0.4元/kWh。
天然气热值取9.7kWh/m³,价格按2.8元/m³计算。
CHP效率:电效率0.35,热效率0.45,热电比在0.8到1.2之间可调。
电储能容量:2000kWh,最大充放电功率500kW,初始SOC为0.2,最后要求回到0.2。
联络线容量:1000kW。
这些参数不需要特别精确,但需要保持一致。有一个我踩过的坑是:热电比约束的方向搞反。CHP在固定热电比模式下,热出力等于k乘以电出力,但如果电出力很小,热出力也会很小,而此时热负荷可能很大。所以要么用可调节热电比模式,要么配合其他热源,否则系统无解。做参数准备时,最好把每个设备的最大功率、最小功率、爬坡速率都列清楚,然后检查是否满足每个时段的负荷总量要求。
3.3 求解器选择与Yalmip接口
Matlab里做优化建模,首推Yalmip,因为它可以用向量化的方式写约束,代码量少很多。求解器我选用Gurobi或Cplex。论文里如果引入了二进制变量(比如设备启停、需求响应时段选择),就必须用混合整数线性规划求解器。如果全是连续变量,用单纯形法或内点法即可。Yalmip会自动识别变量类型,但需要提前安装好对应的求解器,并在sdpsettings里指定。
Yalmip的写法其实很直接。简单示例:
P = sdpvar(24, 3); % 24时段,3个集群的变量 constraints = [P >= 0, P <= P_max * ones(24,1)]; objective = sum(sum(price .* P)); optimize(constraints, objective, sdpsettings('solver','gurobi','verbose',2));因为Yalmip会自动根据目标函数和变量类型选求解器,新手不需要管理求解器细节。但有一个关键点:复数变量、非凸约束一定要避免。论文中的热电比约束如果写成H = k * P,这是凸的线性约束,没问题;但如果写成H*P < constant这种乘积形式,就是非凸,Gurobi会直接报错。
3.4 收敛性判断与迭代流程
分布式优化部分,我采用标准的ADMM迭代。流程是:
初始化交互功率变量z为0,对偶乘子lambda为0。
每个集群独立求解自己的优化子问题,得到本集群期望的交互功率u_i。
更新全局交互功率参考值z = mean(u)。
更新乘子lambda = lambda + rho * (u - z)。
计算原始残差和对偶残差,如果两者都小于阈值,则收敛;否则进入下一轮迭代。
收敛判据我设置为原始残差小于1e-3。同时设置最大迭代次数200;如果超过200次还不收敛,说明罚参数rho可能需要调整。这里有一个实操经验:rho不是越大越好。rho太小,残差下降慢;rho太大,虽然原始残差收敛很快,但集群的独立决策会变得“僵硬”,导致目标函数振荡。一般从0.5开始试,具体还可以配合动态调整。
我完整跑下来,典型情况下迭代40到50轮收敛。对比集中式求解,分布式解的成本会略高一点点,但隐私性和可扩展性更好。如果你的场景更看重隐私保护,或者集群数量很多,用分布式思路是更合理的。
4. 仿真结果与分析维度
4.1 集群间交互功率的变化
为了看到联合需求侧响应的效果,我设置了两组场景:场景1不带需求侧响应,全部负荷固定;场景2带上联合需求侧响应。
从交互功率图来看,场景1中,集群1在傍晚时段因为光伏退出,需要从集群2输入高达800kW的功率;集群2热负荷占比高,到了夜间燃气锅炉出力很大,电出力有余量,所以可以向集群1送电。场景2中,由于负荷侧参与了价格引导,集群1的部分转移负荷挪到了夜间,晚峰时段对集群2的功率需求下降到620kW左右,下降了约22.5%。这直观反映出联合需求侧响应能够缓解联络线阻塞压力。
这里提醒一句,画交互功率时要注意方向定义。我在绘图时通过矩阵索引统一坐标,否则图上的正负方向会反,很容易误导。建议在代码里定义变量时就把方向约定清楚,并用注释写明“正值为从某集群流向某集群”。
4.2 需求响应前后负荷曲线对比
联合需求侧响应对总体负荷曲线的影响同样明显。我选取了一个典型日,把三类负荷分别统计。电负荷方面,峰时段的峰值从3500kW降到3150kW,降幅约10%,同时谷时段的负荷上升,峰谷差从1300kW缩小到850kW。热负荷方面,由于热网有热惯性,可削减能力不如电负荷那么灵活,但联合响应后热负荷的夜间尖峰也下降了6%左右。
这种削峰填谷的效果会直接影响系统总成本。在我的算例中,场景1的总运行成本约为16.8万元,场景2约为15.2万元,下降了约9.5%。成本下降的来源包括三块:一是峰时购电量减少,二是CHP的热电比运行更贴近负荷需求,减少了燃气浪费,三是需求响应补贴支出小于节省的购能费用。算下来经济性提升还是很可观的。
4.3 算法迭代收敛性
分布式算法是否可靠,要看残差曲线。我的ADMM实现中,原始残差从初始值0.35下降,经过20轮后降到0.02,40轮后降到0.001以下,满足收敛条件。对偶残差也同步下降。这个收敛过程不是严格单调的,中间有几次小的反弹,这是罚函数方法的正常现象,不用太紧张。
我建议把迭代过程中每个集群的目标函数值画出来查看。如果某个集群的目标函数一直在剧烈波动,说明该集群存在多个局部最优或者约束设置有问题,优先检查该集群内部的储能SOC约束和热电耦合约束。另外,也可以记录每轮迭代后交互功率的差值,帮助判断是否需要调整rho。
5. 常见问题与排查技巧
5.1 求解失败(infeasible)
这是复现优化模型时最常遇到的问题。我统计了一下,至少一半的“模型跑不通”都指向约束过强或参数矛盾。排查顺序很重要:
第一步,看求解器返回的报表,找到是哪一个约束被标记为infeasible。Yalmip会把约束组名字打印出来,所以建模时一定要给每个约束组命名,比如constraints = [constraints, P_balance: '电功率平衡']; 这样报错一眼就能定位。
第二步,检查设备参数之间的物理一致性。比如CHP的最大热出力和最大电出力不能同时满足热平衡时,就要调整热电比或者增加备用热源。
第三步,把部分约束先放宽,比如联络线容量从1000kW改为10000kW,看看系统是否无解。如果还是无解,问题多半在储能时序约束或者需求响应削减比例约束。
我习惯用一个“可行性调试文件”,专门把每个约束单独注释,再逐步解开,直到找到第一个导致无解的约束组。这个方法虽然笨,但极其有效。
5.2 收敛缓慢或振荡
ADMM迭代不收敛,常见原因有三个:罚参数rho不合适、交互功率初始值不合适、耦合变量的惩罚项形式不一致。rho太大或太小都会导致问题。我的实测经验是,先固定rho=0.5,观察残差;如果残差在后期一直不下降,就按1.5倍逐次放大rho。还有一种情况是,几个集群对交互功率的敏感性差异大,导致一个集群已经把交互功率推到边界,另一个集群还在内部优化。这时可以考虑对交互功率添加一个小的罚项,让它逐步逼近边界。
另一个容易忽略的点是,每个集群子问题里的需求侧响应约束如果太紧,会导致该集群的可行域很小,在ADMM迭代时很难靠近全局参考值。遇到这种情况,我会先把可削减比例从20%降到10%试试。
5.3 维度不匹配和数据读取错误
Matlab里24×3的矩阵,很多新手直接在xlsread后拿了个1×72的向量,然后Yalmip报错:变量维度不匹配。这个问题看似低级,但在多集群模型里特别容易发生,因为不同表的数据行列顺序可能不一样。我的建议是,读取数据后立刻用size()函数检查维度,并做一次归一化处理。另外,所有集群的时段数要保持一致,如果有集群的数据是48个点,另外是24个点,直接拼起来就会错位。
我在data目录下放了一个“数据维度说明.txt”,把每个Sheet的矩阵维度都写清楚。每次改数据之后跑起来之前,先对照这个说明检查一遍,能省掉很多无谓的报错时间。
5.4 结果异常后的定位方法
当仿真结果出现比如某个设备出力全是0、交互功率全是0,或者SOC曲线忽高忽低,别急着改代码。先看对应变量的数值,用Yalmip的value()函数把关键变量导出,单独对比平衡等式。最典型的例子是SOC:我发现第一次跑的时候,储能从0.2一直放到0.8,但总电量没变,后来发现SOC的递推公式少了一个Δt系数。这类问题,靠肉眼检查很难发现,但生成一个SOC曲线图,一眼就能看出来时间尺度不对。
另一个常用手段是分别打印约束的松弛变量值。如果一个约束本来就是等式约束,松弛变量应该为0,如果明显不为0,说明最优解是通过牺牲松弛变量找到的,对应的约束可能太紧了。
6. 复现过程中的经验心得
这类EI论文复现项目,说实话,真正的价值不是拿现成代码跑一遍,而是把模型的“为什么”想清楚。我在复现联合需求侧响应模型时,最大的感触是:分布式协同优化和需求响应看起来是两个方向,一个供应侧、一个需求侧,但实际是在同一个优化框架里相互作用。如果没有需求响应做缓冲,集群间的交互功率就会很紧张,分布式算法的迭代也更容易振荡;加上需求响应之后,系统可行域变大,求解反而更顺。
还有一个细节是,论文里的“联合”二字,意味着你不能把电响应和热响应分开算。我试过先优化电力系统、再优化热力系统,得到的结果比联合优化高了不少,因为忽略了CHP的热电耦合。这个道理说起来简单,但实际建模时很多人会被“先电后热”的惯性思维带偏。
最后分享一个小习惯吧:我复现这类模型时,会把每个时间段的数据、变量、约束做一次“维数审计”,说白了就是拿纸笔列一个表,看每个矩阵的行列含义。这个习惯一旦养成,后面看再复杂的模型也不会乱。整个项目如果从头开发,预计需要一周左右,如果你已经熟悉Yalmip和ADMM的套路,压缩到两三天问题不大。希望这些踩坑经验对你有用。