多主体综合能源系统主从博弈优化调度:需求响应与电能交互的Matlab实现
开头先亮个观点搞综合能源系统优化调度这个方向光看论文很容易懵真正能跑通的代码才是硬通货。这套题为“计及需求响应和电能交互的多主体综合能源系统主从博弈优化调度策略”的Matlab实现本质上解决的是一个特别现实的问题——当园区里既有冷热电联供机组又有多方用户比如商贸楼宇、工业厂房、居民区大家各有各的利益诉求时电费怎么定价、储能怎么充放、负荷怎么调节才能让上面主导的能源运营商赚到合理利润同时让下面跟随的用户主动配合、总成本最小。听起来像微观经济学里的定价博弈没错这就是这篇文章要聊的核心。需要说明的是这个方案的主从博弈Stackelberg博弈思路适合对微电网优化调度、需求响应建模、多主体协调有需求的研究生、工程师以及准备数学建模竞赛比如2017年电工杯A题那种微电网日前优化调度的同学。文章后面我不仅会把模型一层层拆开讲还会把Matlab里用YalmipCplex/Gurobi落地时那些容易踩的坑都翻出来尽量做到看完能动手。1. 项目整体设计多主体为什么要用主从博弈1.1 多主体综合能源系统到底多在哪里说“多主体”很多刚入门的朋友会以为只是“电源类型多”“负荷类型多”其实不完全。所谓“多主体”核心在于参与决策的利益主体不止一个。比如一个典型区域综合能源系统里有这样的角色园区能源运营商拥有燃气轮机、电锅炉、储能、光伏以及向上级电网购电的通道商业用户聚合商可调空调、照明、电梯等负荷受电价激励调整用电工业用户聚合商有可平移的电炉、水泵、空压机等生产负荷生产任务在时间上可重新安排电动汽车充电站既是负荷也是潜在的灵活性资源充电时间可错峰。这些主体不是从属关系而是互相关联又各自独立的利益实体。园区能源运营商希望卖电价格定高一点、买电价格压低一点把收益留给自己用户则希望电价越低越好最好还能把高耗能工序挪到谷时。如果强行让运营商搞一个“整个园区统一优化”用户没有自主权一旦实际运行中发现自己吃亏就不会按系统的计划去执行最后变成了理论最优、实际落不了地。1.2 集中式优化与主从博弈的取舍集中式优化单层优化假设所有主体听令于一个中央控制器以全系统总成本最小为目标求解经典的方法在微电网日前调度里很成熟——2017年电工杯A题就是这种类型光伏、储能、分时电价都建在一个模型里一次求解出各个时段的功率分配。这种方式计算效率高、全局性好但致命弱点在于没有考虑利益分配如果某个主体的“个体利益”和“整体最优”方向不一致集中式优化给出的方案在现实中根本推不动。主从博弈恰好把这类问题建模成“一主一从、上下递阶”的结构。在这一策略中通常把起主导作用的能源运营商设为上层领导者Leader它先制定价格信号和调度策略用户或微网聚合商作为下层跟随者Follower在给定的价格信号下做自身最优响应。上层在制定决策时必须预判下层的反应两者之间通过“价格杠杆”联动最终达到一个彼此都能接受、具备可执行性的均衡解。这个思路用在“需求响应 电能交互”场景里特别合适。因为需求响应本身就是价格驱动的用户根据电价调整用电行为而电能交互比如分属不同运营商的微电网之间互相买卖功率也天然带有交易属性价格是引导交易量的核心手段。当电价既是上层决策变量又是下层响应依据时用主从博弈来描述就是最贴合业务逻辑的方式。1.3 需求响应和电能交互在博弈里的角色在这个模型里需求响应并不只是“削峰填谷”的软约束它实际上是上层运营商手里的“杠杆”运营商通过设置不同的内部购电价、售电价诱导用户把用电高峰时段的部分负荷转移到低谷时段。用户响应得越多运营商用于应对高峰购电的成本就越低系统需要的备用容量也越小。而从下层用户的角度看需求响应是他们主动参与程度最高的手段——通过调整用电计划换取更低用能成本。电能交互则体现为不同主体之间除了与主电网交易之外还可以进行点对点的功率传送。比如靠近光伏电站的用户聚合商中午光伏出力富余可以选择把多余电能出售给相邻的工业用户而不是全额反送给大电网晚上光伏出力为零时再从其他主体处购入电能。这种交互会形成多个购售电价进一步丰富了博弈结构。需要注意的是引入电能交互之后模型里会出现双线性项——交互功率乘以交互价格这让原本的线性优化问题变成了非线性问题求解难度明显上升。后面我在第3节会专门聊怎么处理。1.4 这套策略的实际应用场景这类模型最适合的落地对象是区域型综合能源系统例如新建的智慧园区、大学城、工业园区、商业综合体群等。它们的共同特征是内部有一定规模的分布式电源和储能用能主体多元且具备可调潜力物理上能通过一条中压或低压配电网络互联有建立内部电力市场或“虚拟电厂”的现实需求。2. 数学模型构建上层定价、下层响应与交互关系2.1 上层智慧能源运营商的优化模型上层决策者的角色通常是综合能源运营商或叫微网运营商它的目标函数一般写成收益最大化主要包含这几部分售电给用户的收入向上级电网卖电的收入向下级用户的购电成本向上级电网购电的成本机组运行成本燃气轮机、锅炉等储能折旧或运维成本。写成数学形式简化示意为max \sum_{t1}^{T} [ \lambda_{t}^{sell} P_{t}^{sell} - \lambda_{t}^{buy} P_{t}^{buy} - C_{t}^{DG}(P_{t}^{DG}) - C_{t}^{ES} ... ]其中 ( \lambda_{t}^{sell} ) 和 ( \lambda_{t}^{buy} ) 是上层决策变量——运营商制定的内部售电价和购电价。它们跟大电网的分时电价不一定相同可以当作差异化价格信号来引导用户。约束条件包括系统功率平衡约束上级购电功率 光伏出力 燃气轮机/储能放电功率 用户总负荷 储能充电功率 上级售电功率交互功率上下限约束运营商与上级电网之间以及多主体之间的传输功率不越限储能充放电功率与SOC状态约束价格约束运营商制定的内部电价不能超过设定范围防止恶意抬价伤及用户。关键逻辑是上层给定一组电价后下层用户会做自己的最优响应而上层必须把这个响应过程“提前预知”并嵌入约束中这正是主从博弈和普通双层优化的本质区别。2.2 下层用户聚合商的优化模型下层用户聚合商的目标函数是把购电成本降到最低。为了更具灵活性模型里会加入三类需求响应资源可削减负荷比如空调、照明在一定限度内可以降低用电功率但会带来用户舒适度损失可转移负荷比如工厂的电炉、洗衣机、蓄热式电锅炉用电时段可以平移但总用电量不变可替代负荷比如同一道工序可以用电也可以用天然气或热力完成牵扯到多能互补这个在综合能源系统里更常见。下层用户的目标函数通常写成min \sum_{t1}^{T} [ \lambda_{t}^{buy} (P_{t}^{base} P_{t}^{shift} - P_{t}^{curt}) C_{t}^{curt}(P_{t}^{curt}) ... ]约束有转移负荷的时间窗和总量约束、削减负荷的比例上限约束、用户总功率平衡等。这里的决策变量用户对运营商给出的电价做出反应后决定了各个时段买多少电、削减多少负荷、转移多少负荷。特别注意在博弈平衡解的构建中下层的这个最小化问题必须是线性规划LP或凸二次规划否则后续KKT条件转换就很难处理。这就是为什么很多论文里把需求响应模型都做了线性化处理比如把用户舒适度损失表示为线性惩罚项而不是用复杂的非线性函数。2.3 需求响应的建模细节与参数选择需求响应不是一句话“用户少用点电”就完事实际建模时有很多容易踩的细节。可削减负荷要定义削减成本系数 ( c_{curt} )不同用户舒适度损失不同。比如商业楼宇削减空调负荷成本系数偏高工业生产线的非核心辅机削减成本系数偏低。数值合理设定会直接决定削减量在哪几个时段出现需要结合分时电价和用户失负荷价值来校准。可转移负荷要记录转移方向和时间窗。一个需要注意的细节是这类约束必须写“转入量 转出量”的总量平衡否则用户为了省钱把所有负荷全挪到谷时模型就会“失真”。实践中最常用的写法是引入二进制变量标记负荷开始时间采用混合整数线性规划MILP表达求解规模会因此涨不少。基于价格弹性矩阵的需求响应也常用尤其当用户群规模大、个体行为分散时。价格弹性矩阵描述的是用户的电需求量对电价变化的敏感度一般取负值表示电价升高时用电量下降。这个方法的优点是不需要精细建模每台设备缺点是高阶耦合关系容易被简化掉误差比直接建负荷模型稍大适合宏观分析。2.4 电能交互建模从单一购售关系到多主体交易电能交互是这个题目中的重头戏。多主体之前的差异体现在不同微网或用户聚合商有各自独立的光伏、储能、负荷曲线。当某主体午间光伏大发、自身负荷偏低它会倾向于把多余电卖给相邻主体或者反送大电网另一个主体午间因为生产需求负荷飙升它更愿意就近买电来平抑购电成本。电能交互建模中交互价格常常是博弈中的“耦合变量”。最简单的主从结构里交互价格由上层运营商统一确定下层只决定交互功率大小这种模式在数学上处理起来相对容易。更复杂的结构下交互价格由参与者双方协商确定模型就变成平衡约束优化EPEC或MPEC问题计算量和收敛难度都会明显增加一般做研究或实际工程时先把前者跑通再考虑扩展。交互功率的约束也有讲究。除了线路容量上下限之外还要注意交互电量的损耗率很多复现代码里为了简化会忽略网损但如果传输距离较远或者线路负载较重网损系数可以取1%~5%会让结果更贴近实际。3. 求解核心KKT条件转换与线性化处理3.1 为什么需要把上下层“捏”成一个问题主从博弈模型天然是双层优化上层解一组变量下层跟着解另一组变量。如果直接按“上层先给价格-下层算响应-再迭代更新价格”的朴素顺序做通常会陷入不收敛或者锯齿形振荡效率很差。更稳健的工程做法是把下层问题用KKT条件替换掉把它变成上层优化的一组额外约束从而把“双层优化”等价转换为“单层带均衡约束”的优化。此时如果上层目标还是线性的问题就转成数学规划含均衡约束MPEC问题接着用强对偶理论消除双线性项最终转成标准的MILP交给Cplex/Gurobi这类商用求解器直接解。3.2 下层问题线性化与KKT条件推导假设下层用户模型是一个线性规划标准形式为min \ c^T x subject \ to \ A x \le b, \ x \ge 0它的拉格朗日函数是 ( L c^T x \mu^T(Ax - b) )KKT条件包含平稳性条件( c A^T \mu 0 )互补松弛条件( \mu_i (a_i^T x - b_i) 0, \ \forall i )对偶可行性( \mu \ge 0 )原始可行性( A x \le b, \ x \ge 0 )。这里互补松弛条件是非线性的需要引入0-1变量和Big-M法线性化例如把 ( \mu_i \le M z_i )( (a_i^T x - b_i) \le M(1 - z_i) )其中 ( z_i \in {0,1} )M需要取足够大的正数。这里有个实操要点M的选择直接影响求解速度和数值稳定性。M太小会切掉可行解M太大则会在求解器内部引起数值病态。复现时应先跑一遍无互补松弛约束的模型观察各变量量级再设定M为最大量级的10~100倍或者使用指示约束功能让求解器自动处理。3.3 双线性项怎么消掉上层模型里会出现 ( \lambda_{t}^{sell} \cdot P_{t}^{sell} ) 这样的双线性项同时下层KKT转换时也会带入对偶变量与决策变量的乘积。处理双线性项的标准路子是强对偶理论。因为下层问题是线性规划在最优解处原始目标值等于对偶目标值这样可以把下层目标中的变量乘积替换为对偶变量与常数项的乘积形式最终实现线性化。这一招在博弈领域非常经典但要特别注意应用前提下层问题必须是线性规划且满足强对偶条件。一旦下层引入整数变量比如可转移负荷的启停标记强对偶就不成立处理难度骤增。所以很多成熟的博弈复现代码都会把下层模型限制为纯线性规划这也是为什么有些论文里把可转移负荷做了连续化近似处理。3.4 求解流程整体串讲主从博弈求解思考路径整理如下初始化参数分时电价、负荷曲线、光伏曲线、储能参数、网络参数、需求响应参数建立上层运营商收益最大化模型建立下层用户成本最小化模型推导下层KKT条件用强对偶消去双线性项用Big-M线性化互补条件合并为单层MILP模型用Yalmip调用Cplex/Gurobi求解校验KKT残差、功率平衡、价格合理性输出电价、调度功率、需求响应量、储能SOC等结果。如果不用MPEC直接求解而是采用分布式迭代方法如交替方向乘子法ADMM、嵌套迭代则每次迭代都要分别求解上层和下层问题再通过更新乘子或电价修正收敛判据要参考连续迭代之间的电价差和功率差一般取1e-4或1e-6作为容差。4. Matlab代码实现与实操细节4.1 代码整体框架与工具箱选择Matlab里建这种优化模型强烈建议用Yalmip工具箱再搭配Cplex或者Gurobi。Yalmip并不是求解器它是建模语言作用是把你的模型翻译成求解器能懂的标准格式。Gurobi和Cplex是学术界和工业界主流的商用求解器Gurobi对MILP的求解速度通常比Cplex更有优势。如果你暂时没有这两个求解器也可以先用开源的SCIP或HiGHS过渡但对于大规模MILP求解性能差距很大。代码结构大致分成四个文件main.m主程序入口设置参数、调用建模函数、求解并输出结果 -参数设置函数或脚本包含所有输入参数的初始化模型构建函数上层目标、下层KKT转换、合并、加约束结果处理与绘图脚本绘制电价、功率、SOC等曲线。4.2 主从博弈建模核心代码骨架这里给出一段示意代码说明主体结构非完整可直接运行版本% main.m 主程序 clear; clc; close all; %% 参数定义 T 24; % 调度时段数单位h % 负荷、光伏曲线、分时电价、储能参数等 P_load [ ... ]; % 基础负荷 P_pv [ ... ]; % 光伏预测功率 price_grid [ ...]; % 上级电网分时购电价 %% 决策变量 lambda_sell sdpvar(1, T); % 运营商售电价 lambda_buy sdpvar(1, T); % 运营商购电价 P_ess_ch sdpvar(1, T); % 储能充电功率 P_ess_dis sdpvar(1, T); % 储能放电功率 P_user sdpvar(1, T); % 用户购电功率 % ... 这里继续补充各类下层变量与对偶变量 %% 目标函数 Objective ... ; % 上层收益 - 运行成本 %% 约束 Constraints [ ... ]; % 上层功率平衡、价格上下限、储能SOC %% 求解 ops sdpsettings(solver,gurobi,verbose,2); optimize(Constraints, -Objective, ops); %% 结果输出 plot(1:T, value(lambda_sell), r-); hold on; plot(1:T, value(lambda_buy), b--);真正落地时代码里最麻烦的部分不是写目标函数而是把下层KKT条件和线性化逻辑用Yalmip语法准确地表达出来。Yalmip提供了“dual”赋值方式用于提取对偶变量也能直接处理一些大型约束但互补松弛条件还是要靠Big-M手动展开。4.3 储能约束与SOC更新储能模型是比较容易写错的地方。正确的SOC递推约束是% 储能SOC约束 soc(1) soc_init P_ess_ch(1)*eff_ch - P_ess_dis(1)/eff_dis; for t 2:T soc(t) soc(t-1) P_ess_ch(t)*eff_ch - P_ess_dis(t)/eff_dis; end Constraints [Constraints, soc_min soc soc_max]; Constraints [Constraints, 0 P_ess_ch P_ess_ch_max]; Constraints [Constraints, 0 P_ess_dis P_ess_dis_max]; Constraints [Constraints, P_ess_ch(1)*P_ess_dis(1) 0]; % 防止同时充放电如果写成连续模型而且不约束同一时段不能同时充放电求解器会出现“既充电又放电”这种没有物理意义的解。在MILP里通常引入两个二进制变量 ( u_{ch}, u_{dis} ) 来保证互斥u_ch binvar(1, T); u_dis binvar(1, T); Constraints [Constraints, u_ch u_dis 1]; Constraints [Constraints, 0 P_ess_ch P_ess_ch_max .* u_ch]; Constraints [Constraints, 0 P_ess_dis P_ess_dis_max .* u_dis];另外SOC初值和末值在日前调度里经常设置相等也就是一天循环下来储能电量恢复原位这个在实践里看需求可以加也可以不加。4.4 需求响应代码实现技巧需求响应部分最常见的实现方式是把可转移负荷总量分摊到各时段用连续变量表达转移量然后加总量约束P_shift_in sdpvar(1, T); % 转移进来的负荷 P_shift_out sdpvar(1, T); % 转移出去的负荷 Constraints [Constraints, sum(P_shift_in) sum(P_shift_out)]; % 总量守恒 Constraints [Constraints, P_shift_in 0, P_shift_out 0]; Constraints [Constraints, P_shift_in P_shift_max];如果考虑用二进制变量表示“某时段是否启动可转移设备”约束会更复杂一些例如z_start binvar(1, T); % 启动标记 % 转入负荷只能在设备启动后发生 Constraints [Constraints, P_shift_in(t) M * z_start(t)];这里需要注意M的取值建议先用一个大数初测再用求解器报告里的“最大约束违背”调整往往能有效减少数值问题。4.5 与2017年电工杯A题等单主体题的对比很多朋友问过这个模型和2017年电工杯A题“微电网日前优化调度光伏储能分时电价”有什么区别区别其实就在“多主体”和“博弈”这两个词上。2017电工杯A题是典型的单层日前调度光伏、储能、负荷、分时电价全在同一个目标函数里唯一要做的就是把成本最低的调度方案算出来。而主从博弈模型里电价不再是一成不变的输入参数而是上层优化出来的决策变量下层用户又会反过来影响这个决策。换句话说电工杯A题“算”一次就行主从博弈需要“你来我往”到收敛点认知维度和计算复杂度都上升了一截。建议新手先拿2017电工杯A题的模型练手跑通单主体调度之后再往多主体博弈扩展这样梯度小一些排查问题也更容易。5. 常见问题与排查技巧实录5.1 求解器报Infeasible模型不可行这是最经典的问题。遇到不可行先做下面几件事检查功率平衡约束把所有产生电量的项和消耗电量的项逐时对比很多不可行都是符号写反了或者漏了某项检查储能SOC初末约束尤其当日末SOC强制等于初值时如果储能容量太小可能出现无法在一天内完成充放循环的情况此时适当降低末值比例或放宽SOC上下限检查Big-M是否过小M过小会直接切掉可行域。遇到怀疑时把M放大10倍再试用Yalmip的检查命令check(Constraints)会逐条列出约束是否可行迅速定位是哪一组约束出了矛盾。实操时我习惯先把所有约束不变量比如互斥约束、平衡约束逐个离散检查再把KKT转换加进来。一下子全写上出问题根本不知道从哪查。5.2 迭代不收敛或者结果震荡如果是采用迭代式博弈解法非MPEC单层化典型问题是上层电价信号变化太大下层响应剧烈下一轮上层调整又反向修正于是形成锯齿形震荡。解决技巧对电价更新做阻尼处理( \lambda_{new} \alpha \lambda_{new} (1-\alpha) \lambda_{old} )( \alpha ) 取0.2~0.5对参与者的停电量和交互功率做低频滤波设置合理的收敛判据同时看电价变化量、功率变化量、目标函数变化量三轮连续小于阈值才算收敛。如果你用的是MPEC单层化求解一般不存在迭代震荡问题但可能出现另一个问题求解器返回“locally infeasible”或者MILP节点爆炸优化到一半卡住不动。这往往是Big-M和模型规模的双重压力建议优先精简下层约束临时把24时段改成12时段甚至6时段试算检验模型结构是否合理。5.3 KKT线性化后变量爆炸怎么办下层问题越复杂KKT转换后增加的变量和约束就越多。一个24时段、含储能、可转移负荷、可削减负荷、电能交互的下层模型转换后变量数可能轻松破万对求解器内存和时间是个考验。应对的策略有简化下层模型例如将可转移负荷按峰、平、谷三档集中处理不要精细到每个时段减少时段分辨率从1小时步长改为2小时步长在做论文参数敏感性分析时足够用主动削减冗余变量例如储能充电功率和放电功率的时间序列如果通过互斥绑定可以合并为一个有符号变量表达减少二进制变量数量如果Gurobi求解时间过长调整MIP容忍度MIPGap0.01或0.02很多场景下1%~2%的次优损失完全可接受但求解时间可能缩短几倍。5.4 结果不合理电价异常、储能不工作调试代码时容易碰到的几类“诡异结果”运营商定价过高或过低一看就是价格上下限约束没加或者价格与购电成本之间的联动约束缺失。正常结果中运营商制定的售电价应当高于其边际购电成本购电价应当低于用户的边际用能价值否则博弈没有合理均衡储能几乎不用检查储能收益空间。如果峰谷电价差不大于储能充放损耗成本储能当然不会动作这不是bug反而是合理响应。如果希望储能发挥作用应该调整运营商与上级电网之间的电价结构或储能效率参数需求响应量全时段为0多半是削减成本系数设置太高导致用户宁可买高价电也不削减负荷。处理方法是把削减成本系数调到同类文献参考值附近常见范围在0.3~1.0元/kWh之间具体看用户类型交互功率恒为0检查交互价格是否有套利空间。如果没有价格差或者交互损耗设置过高模型自然判定不交互。5.5 Yalmip/Cplex/Gurobi环境对接坑Matlab版本、Yalmip版本、求解器版本三者必须兼容。Gurobi和Cplex都有官方提供Matlab接口但安装时很容易忽略路径配置。推荐的做法是依次安装Gurobi或Cplex然后在Matlab中运行addpath把安装目录下的matlab文件夹加进来再用yalmiptest命令验证是否成功识别求解器。如果遇到“No appropriate method, property, or field”之类的报错99%是求解器版本与Matlab版本不匹配或者Yalmip没有正确加载。把Yalmip更新到最新版并用clean up清除缓存变量往往能解决大部分环境问题。结尾既然是做实战研究最后再聊点个人的经验和建议我自己在跑这个模型时最大的一个体会是不要一上来就追求“完整复现论文里所有花哨功能”。主从博弈模型本身逻辑链条长上下层耦合又紧代码量一旦铺开调试成本呈指数上升。先去一个小规模系统——比如3个时段、2个主体、1台储能把上层定价、下层响应、KKT转换、线性化整个链路跑通再逐步扩展到24时段、多主体、多能互补。另一点建议是关于“结果合理性”的验证。模型求解完成不等于结果可用一定要拉着实际场景检验如果你自己是运营商这个定价策略用户能接受吗如果你是用户这个响应方案成本真的最低吗只有博弈双方都能在结果里找到合理解释算法才算真正成立。后来我又试着在这个模型上往外延伸过几种玩法把电动汽车集群作为灵活负荷纳入下层把碳交易成本写进上层目标把天然气网和热网的耦合约束加上去。每一次扩展主从博弈的框架都不用打大改只需要在上层或下层里追加决策变量和约束即可。这也是我推荐这个模型作为“研究骨架”的原因——它足够稳定扩展性也够强。如果你正在从单主体调度往多主体博弈跨步或者论文/竞赛复现卡在代码这关希望这篇文章能帮你少踩几个坑。算法的路就是这样多跑多调前面熬过的夜最后都会变成图里那条平滑的均衡曲线。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →