十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于主从博弈的新型城镇配电系统产消者竞价:模型、求解与Matlab复现

基于主从博弈的新型城镇配电系统产消者竞价:模型、求解与Matlab复现 现在做配电网方向研究的人但凡课题里涉及到分布式光伏、储能、电动汽车这些元素基本都绕不开“产消者竞价”这四个字。我之前复现过一个基于主从博弈的新型城镇配电系统产消者竞价项目Matlab代码前后调了两周也顺手翻过市面上各种标着“1029-基于主从博弈的新型…”字样的资源包。这类代码有个共同特点模型看着完整但拿到手之后真正的折腾才刚刚开始——上下层怎么衔接、价格变量怎么初始化、结果为什么震荡、KKT条件转化后为什么无解每一个环节都可能让新手卡上一周。这篇文章我就把这套东西的模型、代码、求解思路和调试经验整理出来给准备做这个方向或者正在复现代码的同学做个参考。1. 先搞清楚一件事主从博弈在城镇配电系统里到底博弈什么1.1 为什么是“城镇”配电系统而不是输电网或传统配电网城镇配电系统的特点是“源荷双侧都活跃”。传统配电网里用户是被动用电的电力公司只管卖电用户没有选择的余地但新型城镇配电网里大量用户变成了“产消者”——家里装了屋顶光伏、储能电池甚至还有电动汽车作为可移动负荷。电价高的时候这些用户愿意多卖电电价低的时候他们倾向于多买电、给储能充电。这个时候电网不能再按照固定电价来结算了需要设计一套“基于价格信号”的互动机制让产消者主动调整用电行为。这个场景比单纯的微电网研究更复杂因为它不是一个小范围内的孤岛而是要跟外部配电网进行功率交换要考虑线路潮流、节点电压、网络拓扑约束。所以“新型城镇配电系统”在论文里通常被建模成一个多节点的配电网比如IEEE 33节点系统作为基础算例在每个节点上挂产消者而不是把整个系统简化成单节点。这也是Matlab代码里通常会先出现一个节点数据矩阵的原因。1.2 产消者竞价本质上是Stackelberg博弈如果你去翻参考文献会发现几乎所有“基于主从博弈”的配电网文章讲的核心都是Stackelberg博弈也叫主从博弈。这个博弈结构非常契合电力市场的实际决策顺序上层领导者比如配电系统运营商或者售电公司先发布一个内部价格信号比如分时电价或者节点边际电价下层追随者各个产消者在这个价格信号下做自身的用电优化选择买电还是卖电、储能充还是放上层再根据下层所有产消者的优化结果调整价格最大化自身收益。这就是一个典型的“我先出牌、你跟着出牌、我看了你的牌再改牌”的循环过程。和普通博弈的区别在于上下层不是同时决策而是有严格的先后顺序上层拥有“先动优势”。用数学语言表达上层决策变量是价格下层决策变量是用电计划下层问题以上层的价格作为参数而上层目标函数又以下层的响应结果作为输入二者互为嵌套。1.3 这类代码通常对应的是哪一类论文问题网上流传的那批资源标题里带“新型城镇配电系统产消者竞价”的源头基本是某几篇中文核心期刊论文。这类论文的标准配置包括一个上层模型配电系统运营商制定内部购售电价目标通常是最大化自身收益也可能兼顾削峰填谷或降低网损。一个下层模型每个产消者以用电成本最小化或收益最大化为目标在给定电价下安排光伏、储能、柔性负荷出力。一个求解算法由于双层模型一般无法直接求解常用Karush-Kuhn-Tucker条件把下层问题转为上层约束或者用粒子群算法迭代嵌套调用下层优化。Matlab代码要复现的正是这一整套逻辑。很多人第一次拿到代码时习惯先找“结果图”对应的脚本这是误区。真正值得花时间的是把上层的目标函数和下层的优化问题在纸面上对应起来否则你在代码里连哪个是决策变量、哪个是参数都可能搞混。2. 上下层模型怎么搭建目标函数与约束条件的取舍2.1 上层配电系统运营商“怎么定电价”上层决策者可以是配电系统运营商也可以是聚合售电公司。大多数论文里上层的目标函数写成“从外部电网购电的成本 向下层售电的收入 从产消者购电的成本”不管业务逻辑怎么绕最后要优化的都是综合收益。代码里常用这样一段伪逻辑% 上层决策变量每个时段每个节点的购售电价 price_purchase sdpvar(24, N_node); % 购电价 price_sale sdpvar(24, N_node); % 售电价 % 上层收益售电收入 - 购电成本 revenue sum(sum(price_purchase .* P_buy_from_grid ... - price_sale .* P_sell_to_grid));这里有一个特别容易搞混的点上层对产消者是“买”还是“卖”方向不同价格的含义完全不同。产消者把多余光伏卖给配电网上层是花钱买产消者缺电时从配电网买上层是收钱卖。很多代码里把这两个价格合成了一个“净电价”看起来简化了模型但实际上违背了主从博弈的本意。因为如果只有一个综合电价产消者的决策空间会从二维变成一维下层的价格响应能力被大大削弱最终算出来的均衡点很可能只是一个次优解。我做项目时倾向于保留双向价格并且在上层目标函数里额外加一个网损项。网损虽然不直接出现在博弈目标里但如果不加上层在低负荷时段会把购电价压得极低导致储能系统疯狂充电结果节点电压越限算出来的解根本不可行。2.2 下层产消者“怎么响应电价”下层每个产消者都是一个独立的优化问题。它的决策变量有从配电网的购电量向配电网的售电量储能充电功率和放电功率柔性负荷的启停或转移量光伏出力如果允许弃光这也是决策变量目标函数通常是购电费用与售电收益之差的最小化即objective sum( price_sale(t).*P_buy(t) - price_purchase(t).*P_sell(t) ... C_storage.*(abs(P_ch(t)) abs(P_dis(t))) );很多代码里会忽略储能的使用成本这是一个会导致仿真结果“过于完美”的坑。储能电池每充放一次都有寿命损耗如果不把这个成本放进目标函数算法会倾向于让储能高频充放用来套取峰谷价差结果就是每天储能循环次数高得离谱实际工程中根本不可能接受。下层约束里储能SOC是必写的而且要注意时序耦合。SOC约束写起来不复杂但容易漏掉初始时刻的荷电状态以及在调度周期末的SOC恢复约束。后者在多数论文里都存在目的是保证储能电池在一个调度周期后回到初始状态这样算出来的调度方案才是可持续的。2.3 约束条件里最容易被忽略的几个边界问题上下层模型分别建好之后真正的分水岭在于约束条件的完整性。我见到最多的问题有三个。第一个是节点功率平衡约束。城镇配电系统是多节点的不能只看配电网根节点的总功率平衡还要考虑每个节点的注入功率关系。如果不加节点功率平衡即使上层和下层各自都收敛了得到的“均衡”也无法在物理上实现。正确做法是用DistFlow方程写出每个节点的有功、无功平衡。第二个是价格上下限约束。上层定价如果没有任何约束博弈过程中价格可能出现负数或者超过用户承受能力的离谱值。实际代码里价格需要限制在“外部电网购电价乘以一个比例系数”的范围内比如0.5倍到1.5倍之间。第三个是购售电互斥约束。同一时刻同一个产消者不应该既买电又卖电。这个约束在数学上可以用二进制变量来表示但二进制变量会显著增加求解难度。很多复现代码是通过设置一个很小的“死区”来规避的就是在目标函数里给购售同时发生的情况一个惩罚项。这个方法简单有效但惩罚系数设置必须合理太小没有约束力太大会扭曲最优解。我通常把惩罚系数设为电价上限的1.2倍实测效果稳定。3. 求解思路KKT条件法和粒子群嵌套法怎么选3.1 把双层博弈转成单层MPECKKT条件转化法主从博弈问题不能直接用现成的优化求解器硬解因为它们不是单层优化。最经典的数学处理思路是把下层优化问题用KKT条件代替这样下层就变成了一组约束条件整个问题就变成了一个单层的带均衡约束的数学规划问题即MPEC。在Matlab里的实现通常是手工推导下层问题的拉格朗日函数然后对每个决策变量求偏导得到一组互补条件。这个过程非常容易出错因为产消者数量一多决策变量数量可能达到几百上千个人工推导几乎不可行。常见的做法是用YALMIP的kkt命令自动生成下层问题的KKT条件Constraints [Lower_Constraints, kkt(Lower_Problem, Lower_Variables)]; ops sdpsettings(solver,gurobi,kkt.dualbounds,1e-6); optimize(Constraints, Upper_Objective, ops);这里有一个大坑YALMIP自动生成的KKT条件里包含大量互补松弛条件这类条件本身是非线性的需要引入二进制变量和大M法进行线性化。大M参数如果设置不当会出现“看起来有解但解不满足原问题”的异常情况。M值太小会切断可行域M值太大会导致数值病态。经验做法是先跑一遍查看对偶变量的数量级把M设置为对偶变量最大值的10到100倍。3.2 智能算法嵌套求解粒子群写起来更灵活KKT转化在数学上严谨但实现难度高、求解速度慢。在工程复现和Matlab教学中更常见的是用智能算法迭代上层用粒子群算法或者遗传算法搜索价格策略下层用linprog或quadprog直接求解每个产消者的凸优化问题然后把求得的购售电量返回给上层计算上层的适应度。这个方案的代码结构非常清晰大概长这样for iter 1:max_iter for t 1:24 % 粒子群更新电价 price PSO_position(iter, :); for k 1:N_prosumer % 下层求解给定电价产消者做优化 [P_buy(k, t), P_sell(k, t)] solve_prosumer(price, data{k}); end end % 根据所有产消者的响应计算上层收益 fitness -calc_operator_income(price, P_buy, P_sell); % 更新粒子速度和位置 PSO_velocity update_velocity(PSO_velocity, price, pbest, gbest); end粒子群嵌套求解的好处是代码易读、容错率高下层模型稍微复杂一点也可以直接扛住。缺点是“算法收敛性没有保证”粒子群很容易陷入局部最优而且每次迭代都要调用几十次下层优化速度慢。对于写论文来说这两种方法各有受众。如果你的论文重点是机制设计建议用KKT转化法看起来更“数学”如果你的重点是结果分析和策略优化可以用粒子群嵌套法调参更灵活改算例也更方便。3.3 Matlab环境里求解器怎么选Matlab自带了fmincon、linprog、intlinprog这些求解器但主从博弈模型一旦叠加了二进制变量和互补约束自带求解器往往力不从心。我的实际经验是至少装一个Gurobi或者CPLEX然后通过YALMIP调用。这里有一个装机细节YALMIP不是求解器它只是一个建模语言真正算问题的是底层求解器。很多人把YALMIP当成求解器来装结果一跑就报“No solver found”其实是没装Gurobi或者没添加路径。Gurobi有学术授权学生拿学校邮箱申请一般一天之内能下来装完之后在Matlab里运行gurobi_setup再重启YALMIP就可以了。如果实验室装不上Gurobi退而求其次可以用sedumi或sdpt3这两个是免费的开源求解器处理KKT转化后的小规模问题尚可但节点数一多比如超过33节点、产消者超过30个求解时间会指数级上升。4. Matlab代码的主干流程从算例设置到结果输出4.1 拿到代码后第一件事理清文件结构不管是自己写还是复现别人下载的资源包这类项目的Matlab代码通常都会拆成一组脚本和函数。我建议拿到代码后第一件事不是运行而是打开文件夹看结构。一个完整的项目通常包含下面这几种文件文件类型典型文件名作用主入口脚本main.m定义算例、调用上下层求解、汇总结果数据生成case33.m/load_data.m生成节点网络参数、负荷曲线、光伏曲线上层模型upper_level.m价格初始化、目标函数计算、上层约束下层模型lower_prosumer.m单个产消者的优化模型迭代求解solve_stackelberg.m主从博弈迭代主循环绘图脚本plot_result.m输出电价、出力、SOC等曲线有些资源包会把所有东西写在一个几百行的main.m里这种代码维护性很差但好处是逻辑连贯。处理方式很简单先把脚本从头到尾读一遍在注释不足的地方自己补上“这段在算什么”的批注然后再跑。4.2 关键模块内部到底做了什么我以主入口脚本为例把核心流程拆开讲。第一步是数据准备。城镇配电网的典型算例一般用IEEE 33节点或者PGE 69节点系统需要准备的数据包括一天24小时每个节点的基础负荷、光伏出力的时间序列、储能容量和初始SOC、电动汽车充电需求等。%% 数据准备 mpc loadcase(case33); load(load_profile_24h.mat); load(pv_profile_24h.mat); N_node 33; T 24;第二步是初始化上层价格。这是整个代码里最容易被忽略的环节。粒子群算法对初始种群非常敏感如果价格初始化范围太窄可能只能搜到局部最优范围太宽收敛时间会拉长。我习惯用“外部电网实时电价的上下浮动区间”作为初始种群生成范围这样一开始的粒子就不会离可行域太远。第三步是主从博弈迭代循环。在这个循环里上层价格下发到每个产消者下层独立求解各自的优化问题再把结果汇总回上层。终止条件有两种常见写法第一种是价格迭代变化小于阈值比如连续两次迭代的价格差小于1e-4第二种是上层收益的变化小于阈值。两种都合理但要注意不要只盯一个指标因为价格震荡时收益可能看起来稳定实际上还没有达到均衡。第四步是结果汇总与可视化。除了画电价曲线还要画各节点的购售电量、储能SOC曲线、节点电压分布。这些图是论文里最常用的几张。4.3 我一般在绘图脚本里额外画什么很多资源包自带的绘图脚本只画了最基础的电价和出力曲线。但对于写论文来说几张针对性更强的图往往更说服力。一是我会画“产消者净负荷曲线”随迭代次数的变化用来展示主从博弈的收敛过程。产出这样一张图审稿人一看就知道你的算法是收敛的而不是模型设计完就直接跳过。二是画“不同博弈机制下的社会福利对比图”。比如把主从博弈的均衡结果跟“固定电价”模式下的结果做对比突出主从博弈在削峰填谷和提升产消者收益上的优势。这个对比在论文里几乎是加分区因为主从博弈代码的数值结果本身并不惊艳只有对比才能体现机制设计的价值。三是画节点电压分布特别是光伏渗透率高的时段。城镇配电网里分布式光伏容易造成电压越限如果模型里没有电压约束结果图反而会暴露这个问题所以在绘制前要在模型里把电压上下限约束补上否则论文答辩时被问起来很难解释。5. 跑代码时一定会遇到的几个坑和排查思路5.1 迭代震荡电价在两个值之间反复横跳主从博弈最典型的失败模式是迭代不收敛电价在某个区间内来回震荡像钟摆一样。这种现象的根源通常是上层目标函数在下层响应曲线上存在多个局部极值或者下层的价格响应函数不连续。排查路径第一步把每次迭代的价格变量保存下来画出“价格-迭代次数”曲线。如果曲线是等幅震荡说明算法没有“阻尼”机制第二步检查下层产消者模型有没有整数变量比如可平移负荷的0-1变量整数变量的存在会让下层目标函数不连续上层梯度信息彻底失效第三步在粒子群算法里增加惯性权重衰减或者在迭代更新时引入松弛因子比如新价格 0.7新价格 0.3旧价格通常能明显缓解震荡。这种阻尼处理方法在文献里叫“价格平滑”虽然看起来不高级但在工程复现中极其重要。我在一次实验中把松弛因子从0.5调到0.8收敛迭代次数从400多次降到90多次效果立竿见影。5.2 KKT条件转化后无解问题大概率出在大M参数用YALMIP的kkt命令时最常见的报错是求解器返回“infeasible problem”。初学者第一反应是模型约束冲突但真正的原因往往是互补松弛条件线性化时的大M参数设置不合理。排查方法第一步把互补约束单独提出来检查对偶变量的数量级。具体做法是把问题中的大M初始设为100跑一次求解查看对偶变量的输出范围第二步如果对偶变量最大值是0.1量级那把大M设为10就够了如果对偶变量本身是连续无界的那大M怎么设都救不回来只能回去检查下层模型的约束是否有问题。另一个容易导致无解的原因是电压约束设置过严。IEEE 33节点系统默认电压标幺值允许范围是0.95到1.05但光伏出力高峰期部分节点电压会逼近上限。如果上层模型里强制电压不越限同时价格上限又太低产消者没有动力削减光伏出力整个问题就会无解。我通常的做法是允许电压在0.93到1.07之间波动并把电压越限的惩罚项加入目标函数而不是硬约束。5.3 修改算例规模时参数不要只改节点数很多人复现代码时想把33节点改成69节点直接把节点数改了就跑。结果模型要么收敛极慢要么直接发散。原因在于系统规模变化后很多参数都需要联动调整线路阻抗参数需要重新生成不能沿用原节点数据外层价格的范围要根据新系统的负荷水平重新标定粒子群的种群大小和迭代次数需要增加因为决策变量的维度变大了储能参数如果每个节点都安装总储能容量增加SOC约束的耦合性更强可能需要减小步长。我建议改算例时先跑一个“无博弈模式”也就是固定电价下所有产消者独立优化验证数据和约束都正常再切换到主从博弈模式。这样能把“参数设置错误”和“博弈算法问题”分开定位不会把半天时间浪费在排查一个其实很简单的节点数据错误上。5.4 代码运行很慢瓶颈在下层求解次数当产消者数量从10个增加到50个时粒子群嵌套算法的运行时间会从几分钟暴涨到几个小时。原因是每次迭代都要调用50次下层优化而每个下层优化又是一个含储能的时序问题。针对这个瓶颈有三条优化路径。第一条是改求解器。把默认的linprog换成Gurobi的quadprog接口单纯形法和内点法的性能差异非常明显。第二条是减少不必要的时段颗粒度。有些代码里把调度周期切成了96个时段15分钟一个点但上层博弈价格一天只需要24个时段就够了。下层内部可以沿用96时段但上下层交互时聚合成24时段可以大幅降低迭代计算量。第三条是并行化。Matlab的parfor可以很方便地把不同产消者的下层优化并行执行。我实测在8核机器上50个产消者并行后每次迭代时间从6秒降到2秒左右整体提速接近三倍。6. 从复现到改进怎么把别人的代码变成自己的成果6.1 在算例里加入自己的“新元素”很多同学拿到的代码复现之后发现写论文还是没素材核心问题是“没有自己的边际贡献”。这里分享一个通用的破题思路在基础模型上加一个别人代码里没有的物理元素或市场机制。比如基础代码里只有光伏和储能你可以加入电动汽车的充放电调度把EV作为移动储能接入配电系统产消者决策变量里就多了“EV充放电计划”。再比如基础代码里用的是固定分时电价你可以改成需求响应后的动态电价或者加入碳排放约束让产消者不仅要考虑电费还要考虑碳排放配额交易成本。这些改动不需要推翻原代码只需要在下层模型中增加一组决策变量和相关约束在主程序中增加对应的数据输入就能形成一个新的研究方向。这也是这类复现代码最有价值的地方。之前我接手的一份代码里只有储能的SOC约束我照着加入EV之后整个博弈模型从“单类产消者”变成了“多类产消者”写出来的论文明显比单纯复现要有说服力。6.2 结果图的“可信度”很重要复现代码之后最怕的是结果图被别人质疑。我在实际使用中发现很多网上流传的主从博弈代码产消者数量少时收敛很快但结果图里的用电曲线在几个时段有明显的跳变不像真实数据。为了避免这类问题我建议给结果曲线加上平滑性处理或者对突变时段的调度方案做人工校核确认是电价变化导致的理性响应而不是优化器的数值误差。另外一定要保存每一轮迭代的中间结果。Matlab的save命令用起来很简单但很多初学者总是忘记在循环里定期保存进度。一旦程序跑了两个小时最后崩溃又要从头再来。我习惯在每50次迭代后自动保存一次工作区这样即使挂机过夜也不怕。6.3 运行环境建议最后说一句环境问题。Matlab版本对这类代码的影响比想象中大尤其是YALMIP不同版本对sdpvar处理有差异老代码在新版本上跑经常报错。我目前用的组合是Matlab R2023b YALMIP R20230622 Gurobi 10.0跑下来最稳定。如果你用的Matlab版本过高且之前装了旧版YALMIP建议先卸载干净再装新版路径覆盖不全的话会出现一系列莫名其妙的“Undefined function”报错。如果需要长时间跑仿真建议在脚本开头加上warning(off,all)和tic/toc计时前者减少控制台输出干扰后者方便估算剩余时间。这些都是很小的细节但在实际仿真过程中能省下大量无谓的等待和排查。做完这些一套基于主从博弈的城镇配电系统产消者竞价代码基本就算从“能跑”升级到了“能写进论文、经得起追问”的状态。
返回列表