
最近把一篇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.0240轮后降到0.001以下满足收敛条件。对偶残差也同步下降。这个收敛过程不是严格单调的中间有几次小的反弹这是罚函数方法的正常现象不用太紧张。我建议把迭代过程中每个集群的目标函数值画出来查看。如果某个集群的目标函数一直在剧烈波动说明该集群存在多个局部最优或者约束设置有问题优先检查该集群内部的储能SOC约束和热电耦合约束。另外也可以记录每轮迭代后交互功率的差值帮助判断是否需要调整rho。5. 常见问题与排查技巧5.1 求解失败infeasible这是复现优化模型时最常遇到的问题。我统计了一下至少一半的“模型跑不通”都指向约束过强或参数矛盾。排查顺序很重要第一步看求解器返回的报表找到是哪一个约束被标记为infeasible。Yalmip会把约束组名字打印出来所以建模时一定要给每个约束组命名比如constraints [constraints, P_balance: 电功率平衡]; 这样报错一眼就能定位。第二步检查设备参数之间的物理一致性。比如CHP的最大热出力和最大电出力不能同时满足热平衡时就要调整热电比或者增加备用热源。第三步把部分约束先放宽比如联络线容量从1000kW改为10000kW看看系统是否无解。如果还是无解问题多半在储能时序约束或者需求响应削减比例约束。我习惯用一个“可行性调试文件”专门把每个约束单独注释再逐步解开直到找到第一个导致无解的约束组。这个方法虽然笨但极其有效。5.2 收敛缓慢或振荡ADMM迭代不收敛常见原因有三个罚参数rho不合适、交互功率初始值不合适、耦合变量的惩罚项形式不一致。rho太大或太小都会导致问题。我的实测经验是先固定rho0.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的套路压缩到两三天问题不大。希望这些踩坑经验对你有用。