☰
基于主从博弈的配电网产消者竞价策略IEEE33节点Matlab复现
2026/9/30 4:47:09 网站建设 项目流程

做EI论文复现这个活儿,最折磨人的往往不是公式看不懂,而是你花了三天把代码框架搭起来,结果一跑就报错,报错信息还指向一堆不熟悉的优化问题求解器。最近不少人问同一个题目——“基于主从博弈的新型城镇配电系统产消者竞价策略(IEEE33节点)”,目标都是想在Matlab里把这篇论文的逻辑真正跑通,但又不知道从哪里下手。这篇我直接把自己梳理过的完整路径写出来:主从博弈到底在算什么、产消者竞价怎么建模、IEEE33节点系统怎么改造、Matlab代码按什么结构组织,以及我踩过的几个大坑和对应的排查链路。适合刚接触配电网优化、准备复现EI论文或者做毕业设计需要出图和仿真结果的读者参考。

1. 先搞清楚这问题到底在求解什么:从EI论文标题说起

1.1 主从博弈在配电系统里究竟在玩什么

主从博弈,翻译成白话就是“你先出牌,我再出牌”。在新型城镇配电系统里,配电网运营商(DSO)制定购电价和售电价,产消者根据这个价格来决定自己买多少电、卖多少电、光伏怎么出力、储能怎么充放。两边不是同时做决定的,而是有先有后,这个先后顺序正是斯塔克尔伯格博弈(Stackelberg game)的核心。

很多初学者一开始会犯一个概念错误:以为主从博弈是“三个人你一拳我一脚地打架”,其实它是一种递阶决策结构。领导者先宣布自己的策略,跟随者在领导者的策略下做最优反应;领导者做决策时会把跟随者的最优反应计算在内。放到配电系统里,DSO就是领导者,产消者就是跟随者。DSO知道如果把电价提高,产消者就会减少购电量、增加售电量,所以DSO在制定价格时不是在猜用户行为,而是把用户的行为函数直接“内置”进自己的决策模型里。

这样一层套一层的结构,写成数学就是双层优化(bilevel programming)。上层是DSO的目标函数和约束,下层是各个产消者的目标函数和约束。想真正复现出论文里的结果,核心就是先把这对上下层关系讲清楚。

1.2 产消者竞价策略的问题定位

“产消者”这个词是“生产者”和“消费者”的组合。过去配电网里的用户只是被动用电,现在屋顶光伏、小型储能、电动汽车充电桩大量出现,用户既从电网买电,也可能在光伏出力充裕时向电网卖电。产消者竞价策略,就是这些产消者如何在给定的分时电价下,决定自己每时段的购售电量和储能充放电计划,使自己的效益达到最优。

光有产消者的自身优化还不够。DSO在定电价时,要保证自己的收益最大,同时还要满足网络潮流约束、电压上下限约束、线路容量约束。这就把问题从简单的“两个用户的买卖”扩展成了“全网范围内所有节点协同优化”的问题。所以这篇论文的完整逻辑是:全网各节点上布置若干个产消者,每个产消者都有自己的光伏出力和负荷曲线,DSO制定一套分时购售电价,产消者做出最优响应,双方达到一个均衡状态。

1.3 IEEE33节点为何成了默认测试场

IEEE33节点系统是配电网研究里最经典的辐射状测试系统,33个节点、32条支路,首端是平衡节点。它的大小非常合适:节点数量足够体现网络拓扑带来的影响,又不至于像IEEE123节点那样让初学者在建模阶段就崩溃。很多配电网重构、分布式电源选址定容、电力市场出清的论文都拿它做标准算例,这也意味着你可以很方便地找到它的原始线路参数、负荷数据和拓扑结构。

在做这篇EI复现时,IEEE33节点系统承担两个作用:一是提供一个标准的网络拓扑,让潮流约束可以写进博弈模型;二是提供一个“城镇配电系统”的骨架,你只需要把其中部分节点改成产消者节点,就能模拟新型城镇配电系统中的用户侧资源。后面我会详细讲这部分改造方法。

2. 数学模型搭建:从双层优化到单层转化的完整路径

2.1 上层领导者:配电网运营商的定价决策

上层模型里,DSO的决策变量是各时段的分时售电价ρ_b,t和购电价ρ_s,t。目标函数通常是最大化DSO的净收益,包括向产消者售电的收入、收购产消者多余电量的支出、从上级电网购电的成本,以及网络损耗带来的成本。写法可以简化成:

max Σ_t ( ρ_self,t × P_load,t - ρ_buy,t × P_extra,t - c_grid,t × P_purchase,t )

其中P_load,t是产消者从电网购入的总电量,P_extra,t是产消者反送电网的总电量,P_purchase,t是DSO从上级主网购电的功率。约束条件是购售电价不能超过用户可接受的范围,例如售电价在0.4到1.2元/kWh之间,购电价在0.2到0.9元/kWh之间,以及购电价要小于售电价,否则会产生套利漏洞。

上层的难点在于网络约束。配电网的节点电压、支路功率都要满足潮流方程,而这部分会直接和产消者的购售电决策耦合在一起。因此上层规划不能只写一个简单的资金平衡,必须把潮流约束一并塞进去,这就涉及后文提到的DistFlow模型。

2.2 下层跟随者:产消者的响应策略

每个产消者i的目标可以写成最小化自己的净费用,包括购电成本减去售电收益,加上储能退化成本。决策变量包括从电网买电的功率P_b,i,t、向电网卖电的功率P_s,i,t,以及储能充放电功率P_ch,i,t、P_dis,i,t。

约束条件有几个常见的必须写全:一是功率平衡约束,光伏出力加上网购电量、储能放电量,要等于负荷加上储能充电量和上网售电量;二是储能SOC递推约束,SOC_t = SOC_t-1 + η_ch×P_ch,t - P_dis,t/η_dis;三是储能SOC上下限和充放电功率上限;四是产消者不能同时购电和售电,通常用0-1变量或者大M法来避免。

值得注意的一点是,论文里往往假设产消者严格按照线性规划来响应电价。这意味着产消者是最理性、完全信息的决策者,不会出现“今天心情不好不想卖电”这种随机行为。他们会在电价高的时候少买电或者卖电,在电价低的时候多买电充储能,这正是竞价策略的意义所在。

2.3 KKT条件与大M法:双层转单层的本质

从数学上处理双层优化的标准办法是:把下层优化问题用KKT条件替换,并入上层问题。KKT条件包含四部分:拉格朗日函数对决策变量的偏导为零(稳定性条件),下层问题的原始可行约束,对偶变量非负条件,以及互补松弛条件。互补松弛条件写成μ×g(x)=0,这是一个非线性等式。

实际求解时,互补松弛条件要通过大M法做线性化。比如μ×g(x)=0可以拆成μ≤M×z和g(x)≤M×(1-z),其中z是0-1变量,M取一个足够大的正数。这样一来,原本的双层非线性规划就转化成了一个单层的混合整数规划(MILP)。M取多大非常讲究,取得太小会限制解空间,取得太大会让数值求解不稳定。我一般取约束对应变量量级的100到1000倍,然后通过灵敏度测试确认结果没有被M的变化影响。

不过要提醒一句,如果下层模型是二次规划的,KKT条件转化后会出现对偶变量和决策变量的乘积项,处理起来会麻烦很多。因此很多EI复现论文会刻意把下层的目标函数写成线性的,比如边际成本取常数,购售电效用取线性函数。你在读论文时可以先看下层目标函数里是否有二次项,如果看到成本系数乘功率的平方,就要有心理准备:这个模型的求解难度和代码复杂度会高一个台阶。

2.4 潮流约束的处理:DistFlow与线性化

配电网是辐射状网络,最常用的潮流模型是DistFlow支路潮流方程。写出来大概是:

P_j = P_i - R×L_i,j - P_load_j Q_j = Q_i - X×L_i,j - Q_load_j V_j² = V_i² - 2×(R×P_i,j + X×Q_i,j) + (R²+X²)×L_i,j

这套方程里存在电压平方项、电流平方项以及电压与功率的乘积项,直接丢给求解器会非常难收敛。常见的处理手法有两种。

第一种是二阶锥松弛,把V²和I²当作新的变量,再通过SOCP凸松弛把非线性约束转成锥约束,然后交给Gurobi或CPLEX求解。这种方法在数学上比较严谨,但代码量稍大。

第二种是更省事的线性化方法:忽略支路无功或假设电压接近额定值,把DistFlow近似成线性方程。这种近似在小负荷场景下误差不大,但产消者光伏出力比较高的节点,电压偏移可能比较明显,线性化结果能不能用需要验证。

我在复现时更推荐先用“分布式交替迭代”的方式绕开复杂潮流:外层DSO给一个价格,内层每个产消者独立做优化,再按节点注入功率调用一次潮流计算,更新网损和节点电压,然后DSO根据潮流结果调整下一轮电价。这样写代码容易理解,结果也稳定,代价是理论严谨性不如KKT单层转化,更适合用来先跑通流程。如果你的目标是把论文的所有公式严格复现出来,那还是得走KKT+潮流约束联合求解这条路线。

3. 基于IEEE33节点的案例改造与参数设计

3.1 原始拓扑与数据表说明

IEEE33节点系统的原始数据网上很容易找到,包含每两个节点之间的线路电阻、电抗,以及每个节点的有功、无功负荷。一般情况下,首端节点1通过一个变压器连接上级电网,被视为平衡节点。剩下32个节点的负荷数据都是固定的,单位是kW和kVar。

在做这篇复现时,我建议先把原始数据整理成一个结构体数组,比如bus(i).Pload、branch(i).from、branch(i).to、branch(i).R、branch(i).X。这样做的好处是后面构建约束时可以直接用循环遍历所有节点和支路,不用写一大堆手算出来的硬编码数字。原始数据还能在Matpower里直接loadcase,不过Matpower的格式更复杂,如果你不打算调用它的潮流计算,直接手动整理一个简单的数据表就够了。

3.2 产消者节点的配置方式

产消者不是每个节点都放。通常的做法是在实际应用场景里选一些有代表性的节点,比如光伏资源好的屋顶节点、居民负荷集中的节点、商业负荷节点。我习惯在节点5、节点18、节点25三处配置产消者,每个产消者包含一组光伏出力和一段居民负荷曲线。

如果论文原文里有明确的产消者分布图,说明它是在配电网节点图上用不同颜色的图标标注了哪些节点属于产消者,你就照着标注来配置。如果没有明确标注,你可以按“渗透率”来设计:假设产消者的负荷占全网总负荷的20%到30%,然后选取不同位置的节点填充光伏和储能参数。分布式光伏出力的时间曲线可以参考典型夏季晴天的辐射数据,峰值在正午12点到14点之间;居民负荷的晚高峰则出现在19点到22点。

储能参数我一般按以下范围设置:额定容量在200到500 kWh之间,充放电功率上限为容量的一半,充放电效率取0.9到0.95,SOC上下限取0.1到0.9。SOC的初始值设为0.5,以保证一天的调度过程中储能不会过早放空或充满导致后续时段不可行。

3.3 分时电价与价格边界设计

分时电价的时段划分和数值区间直接影响博弈结果。常规做法是把一天分成峰、平、谷三个时段:峰时段取10:00到12:00和18:00到20:00,平时段取8:00到10:00、12:00到18:00和20:00到22:00,谷时段取22:00到次日8:00。

DSO初始售电价可以设成峰1.1元/kWh、平0.7元/kWh、谷0.4元/kWh,购电价在对应时段取售电价的一半左右。价格上下限约束是让模型不至于出现极端竞价的重要环节。比如峰时售电价上限设为1.5元/kWh,谷时购电价下限设为0.1元/kWh。如果上下限跨度太大,产消者会在谷时段拼命买电存到储能里,再到峰时段全放出来,虽然这确实是合理行为,但如果储能容量和效率参数不正确,会导致模型出现“无成本套利”的假象,结果画出来的图毫无解释价值。

4. Matlab代码实现的核心模块拆解

4.1 数据结构与初始化

启动代码时我会先做一件事:定义全局的时间尺度和节点参数。时间步长取1小时,一共24个调度时段。用三个脚本文件来组织整个项目:init_data.m负责加载网络参数和产消者数据,build_model.m负责构建优化模型,solve_model.m负责调用求解器并处理结果。

初始化阶段还需要定义一个价格矩阵price_sell(1,24)和price_buy(1,24),以及每个产消者的负荷曲线矩阵load_profile(num_prosumer, 24)、光伏出力矩阵pv_profile(num_prosumer, 24)。把这些数据一次性读进来,后面建模的时候就轻松很多。用结构体而不是脚本书写超长矩阵的好处是:调试时能随时bus(5).Pload查看某个节点的数据,不用在一堆无名变量里翻找。

4.2 求解器选择与YALMIP环境

Matlab自带的优化工具箱能处理线性规划和混合整数线性规划,但处理带二阶锥约束的主从博弈复现案例时,速度和稳定性都明显不够。我个人的首选是YALMIP加Gurobi的组合。YALMIP是一个建模工具箱,它允许你用接近数学公式的语法定义变量和约束,内部再自动转换成求解器需要的标准格式。Gurobi有学术免费许可,在MILP和SOCP问题的求解速度上比Matlab内置求解器快好几倍,尤其是产消者数量超过5个、节点数量超过33个的场景,这个差距会非常明显。

如果你没有Gurobi许可证,也可以先用sedumi或者linprog跑一个小规模案例验证模型思路,跑通了再换商业求解器。我不建议一上来就在没问题的小模型上浪费时间等待求解器吃力地跑二十分钟,这种体验会打击继续复现的信心。

4.3 单层规划构建与求解主流程

构建单层模型的核心步骤是三步:定义决策变量、添加目标函数、添加约束。

决策变量包括DSO侧的售电价和购电价,产消者的购电功率、售电功率、储能充放电功率、储能SOC、以及KKT转化带来的对偶变量和0-1变量。目标函数按照上层模型写,交YALMIP统一处理。约束依次添加:价格上下限约束、产消者功率平衡、储能SOC递推、潮流线性近似约束、KKT条件约束、互补松弛的大M线性化。

写代码时我建议把约束按类型打包,用带中文注释的分块结构。比如:

Constraints = [Constraints, price_sell <= price_sell_max]; Constraints = [Constraints, price_sell >= price_sell_min]; Constraints = [Constraints, ... P_buy >= 0 : P_buy <= P_buy_max];

调试时如果某类约束导致不可行,你可以单独把对应模块注释掉跑一遍,快速定位问题。这在后面排查坑的时候非常有用。

求解完成后,结果都用结构体汇总,建议输出以下内容:各时段购售电价、每个产消者的购售电量、储能SOC曲线、DSO收益、产消者成本、各节点电压幅值。画图时可以画出价格曲线和负荷响应的对比图,这是论文里的主力图表。

4.4 结果输出与可视化

可视化部分最容易出效果也最容易出问题。常见图表包括三张:第一张是分时购售电价柱状图和产消者净负荷曲线叠加图,用来体现“电价高时负荷降低”的响应关系;第二张是储能SOC随时间变化的阶梯图,用来验证储能是否合理地在低谷充电、高峰放电;第三张是IEEE33节点网络中各节点电压幅值曲线图,标出电压越限的节点,用来证明博弈结果满足电压约束。

画图时要注意坐标轴的标注单位,论文里经常出现“元/kWh”“kW”“kV”等不同单位混用,如果数据是用标幺值计算的就统一用标幺值,最后把实际值换算回来再输出。我在复现中就出现过一次电压图像看起来完美,但实际数值按标幺值是0.97,按有名值算出来只有9.7kV,明显不对的情况。所以结果输出前,务必做一次单位量级检查。

5. 复现过程中最常见的五个坑与排查链路

5.1 互补松弛条件写错导致模型不可行

这是初学者最容易中招的地方。KKT条件转化后,不是随便把μ×g=0写进约束就能求解。你还需要保证对偶变量和原约束变量一一对应。比如功率平衡约束P_buy + P_pv + P_dis - P_load - P_ch - P_sell = 0是对偶变量λ对应的等式约束,它的KKT稳定性条件是“对每个决策变量求偏导并置零”,而不是对所有变量一刀切。

如果出现模型不可行或者UB/ LB数值极端,优先检查互补松弛条件的变量配对是否正确。我之前排查过一个案例,整整一个下午都在找约束矩阵里的错误,最后发现是储能SOC上下限约束的对偶变量和SOC递推式的对偶变量写反了,导致两条约束互相打架。解决办法很简单:先用一个小规模测试,把所有不等式约束的对偶变量单独列出来,检查每条约束是否都有对应的μ,且μ的非负性没有写错。

5.2 潮流线性化误差导致结果震荡

使用线性化DistFlow时,如果光伏渗透率过高,部分节点电压会显著升高,而线性化模型没有捕捉到电压和功率的耦合关系,就会导致迭代结果在相邻两次循环之间来回震荡:DSO发现节点电压越限,上调电价,产消者减少购电;减少购电后电压还是不满足,DSO再上调电价,结果产消者干脆全部卖电,潮流方向突变,又回到最初的电压状态。

这种震荡的根源是线性近似在大的功率波动区间失真。我建议在复现的第二个阶段换成二阶锥松弛的DistFlow,或者至少保留电压平方项的约束并利用binvar或者sdpvar来处理。如果你只是为了毕业设计出图,不想在潮流上花太多时间,可以退一步:在交替迭代框架中,每轮迭代都调用一次Matpower潮流校验,如果电压幅值越限就按灵敏度方向调整价格,经验上比纯线性化稳定很多。

5.3 价格边界设置不合理导致竞价失真

价格边界是主从博弈模型的“软约束”,但它直接决定解的特征。如果峰谷价差太小,产消者没有任何动力调整负荷,储能也不会充放,仿真出来的“竞价策略”其实退化成一条平坦的购电曲线。如果价差太大,产消者会疯狂利用储能套利,甚至出现“谷时段购买大量电全部充储能,峰时段全部放出”的极端方案,这时DSO的收益可能变成负值。

我发现一个实用的校准方法:先运行一次不包含储能的模型,记录产消者的净负荷曲线;然后加入储能,调整峰谷价差让储能的充放次数保持在合理范围(一天不超过一次完整充放循环)。以此为依据设置价格上下限,可以让模型的解释性变强,图表也更接近真实论文的风格。

5.4 求解时间过长与模型规模爆炸

节点数只有33,按理说模型规模不大。但产消者数量一旦超过5个,每个产消者引入KKT约束和0-1变量后,总的0-1变量数量会迅速增加。特别是SOC上下限约束每时段都有两个不等式,24时段乘多个产消者,就可能产生几百个0-1变量。如果不控制大M的值,求解器在分支定界时会浪费大量时间尝试无效分支。

我后来总结出一个比较实用的做法:先把M的取值根据物理量级固定下来,比如购电功率上限是500 kW,M取5000;SOC偏差上限是0.4,M取4。然后跑一次求解,查看求解完成后是否有约束接近M边界的情况。如果有,适当增大M再跑一次,直到结果不再随M的变化而变化。这个过程不能省,否则你可能会在M取1e6的模型上等几个小时。

5.5 复现结果与论文图表对不上

很多同学在复现完成后发现,自己画出来的图和论文原图走势很像但数值对不上,就开始怀疑自己是不是哪里理解错了。其实这很大概率是因为论文里的算例参数没有完全公开,比如储能初始SOC、光伏装机容量、价格上下限,甚至是负荷曲线的基准值,这些参数缺一个,结果就会有明显偏差。

我的处理策略是:先锁定论文中容易识别的关键结果(通常是“峰时段电价上升,负荷下降”),把不利于复现的细节作为可调参数。通过几个关键参数的微调,比如将光伏容量乘以0.8或0.9,将负荷曲线平移1小时,往往就能把对不上的部分补回来。这个“调参数对图”的过程很费时间,但做完之后你对这个模型的掌握程度会有质的提升。

6. 做完基础复现后的进阶扩展方向

6.1 静态博弈到多时段动态博弈

基础复现通常把一天24小时当作静态模型里的多个离散时段一起优化,但实际运行中,当天光照、负荷的预测值和真实值往往存在偏差。进阶一点的做法是把多时段问题变成一个滚动优化:每个小时开始时,基于最新的光伏和负荷预测,重新求解当前时段到未来4小时的博弈模型,只执行第一个时段的决策,然后滚动前进。这种方法在代码上改动不大,但结果会更有工程参考价值,写论文时的创新点也更明确。

6.2 加入储能与电动汽车负荷

如果想扩展模型复杂度,最直接的是引入电动汽车随机充电行为。电动汽车既是负荷又可能成为移动储能,它的充电时间和充电量具有随机性,通常可以用蒙特卡洛模拟生成若干场景,再通过场景削减方法形成确定性等价模型。这样主从博弈里就多了一层不确定性,解决方案也可以从单阶段优化扩展成两阶段随机优化,DSO在上层决策前需要考虑不同场景下的产消者响应。

6.3 从数学规划到深度强化学习

如果单纯用数学规划求解KKT转化后的单层模型,遇到产消者数量大、约束条件多的情况,求解时间会越来越长。一部分最新的论文开始把主从博弈的学习过程建模成多智能体深度强化学习的框架:DSO用一个DQN或PPO智能体做价格决策,每个产消者用另一个智能体做响应决策,通过多次交互逼近均衡。这种方法的优点是前期不需要严谨的KKT推导,代码上也更接近人工智能方向,适合对深度学习感兴趣的读者。不过要提醒一句,深度强化学习的收敛性很难保证,调参成本非常高,建议先把数学规划版本完整复现一遍,再做这一层扩展。

我自己在实际操作中最大的一个体会是:复现EI论文,不要一开始就追求把所有数学模型和代码一次写完。先把IEEE33节点数据跑通一个最简单的串联流程——DSO定固定价格、产消者做线性优化、输出结果,再一步步加入主从博弈、潮流约束、KKT转化和储能模型。每加一层,就用小规模算例验证一次。这样即使出了错,排查范围也被控制在最新加入的那部分代码里,不用对着几百行约束矩阵从头看到尾。最后再分享一个小技巧:在YALMIP里把约束和变量尽量用有意义的前缀命名,比如con_price、con_soc、con_kkt,生成模型后调用yalmiptest检查每一类约束的稀疏模式,很多隐藏的错误在这一步就能暴露出来。

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

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

立即咨询