首页 / 资讯中心 / 文章详情

主从博弈下的售电商零售套餐设计与多级市场购电策略复现

主从博弈下的售电商零售套餐设计与多级市场购电策略复现 ★ FEATURED ARTICLE
前阵子小组里拿到一个复现任务标题很直白基于主从博弈的售电商多元零售套餐设计与多级市场购电策略要求最终给出可运行的 Matlab 代码。当时我以为是常规的双层优化套件换个背景而已真动手之后才发现光是“多元零售套餐”的离散决策、用户侧需求响应、日前加实时的两级市场结算这三件事凑在一起就已经够绕好几个弯了。这篇笔记我会按建模、单层化、代码实现、调参踩坑的顺序把一套能跑通的主从博弈仿真经验完整写出来也希望给正在复现类似工作的你省点时间。整个项目的核心就三个词主从博弈、零售套餐设计、多级市场购电策略。1. 这个优化问题到底在优化什么1.1 售电套餐与市场购电为什么天然是主从博弈很多人第一次看到“主从博弈”这四个字第一反应是“无非就是双层规划换个叫法”。这话对了一半。主从博弈或者说 Stackelberg 博弈确实经常被建模成双层优化上层决策者先行动下层决策者根据上层决策做最优响应上层再把这个响应预期放进自己的目标函数里。但真正难的地方在于你要想清楚在这个场景里谁先动、谁后动以及为什么这种先后关系符合现实。以售电商为例。售电商一边从批发市场买电一边向终端用户卖电。它的身份是中间商卖电收入减去买电成本就是利润。问题是卖电收入取决于零售套餐价格和用户实际用电量买电成本则取决于批发市场出清结果。用户不是待宰的羊他们会根据套餐价格调整用电习惯电价高就少用一点电价低就把可转移负荷搬到低谷。这时候如果售电商先把套餐价格拍板用户再根据价格决定用多少电那么售电商在定价时就必须预判用户会怎么反应。这种决策顺序就是标准的主从博弈结构售电商是领导者用户是跟随者。我复现时特意跟组里同事确认过这一点为什么不直接做集中式社会福利最大化因为集中模型需要把所有用户的隐私参数、舒适度函数全部集中到优化器里现实中用户根本不会把“我晚上必须开空调到多少度”这种信息交给你。主从博弈的好处是它把用户的行为描述成对价格信号的理性响应更贴近电力零售市场的真实交易规则。所以在模型结构上上层是售电商收益最大化下层是每个用户在自己套餐约束下的用电效用最大化两层通过价格和电量耦合。1.2 多元零售套餐与两级市场采购的耦合点在哪里零售套餐这块“多元”不是花架子。按我复现的那类研究常用套餐一般包括三类固定电价套餐、分时电价套餐、阶梯电价套餐。固定电价最简单所有时段一个价分时电价把一天分成峰、平、谷三段分别给不同价格阶梯电价则是把用电量分成几档用得越多单价越高。这里的难点不是构造价格向量而是套餐选择是离散动作。用户不能同时选两个套餐所以模型里必然要引入 0-1 整数变量比如选中分时套餐为 1否则为 0。这些整数变量会和价格变量、电量变量混在一起直接改变问题的求解难度。多级市场购电则是另一条线。售电商要在日前市场提前申报次日购电量这部分电量在日前出清价格下结算到了运行日实际负荷和日前申报一定有偏差这个偏差要拿到实时平衡市场里买或者卖。所以售电商手里有两组购电变量日前购电量曲线和实时调整量曲线。于是整个优化问题就变成上层先定零售套餐价格和日前购电计划然后用户响应出实际负荷实时市场再处理偏差。套餐设计影响实际负荷实际负荷影响实时购电偏差而实时购电偏差的成本又反作用于上层收益。这个耦合关系是模型的灵魂。第 1 部分我建议你先在纸上画三行第一行写上层变量零售价格、套餐选择、日前购电第二行写下层变量用户各时段电量第三行写耦合关系即用户电量同时出现在用户目标函数和售电商收益函数中。这一张图画清楚后面写代码就不至于迷失方向。2. 从双层博弈到可求解的单层模型2.1 用户跟随者问题的完整表达与最优条件把下层的用户问题写清楚是单层化的前提。每个用户的决策变量是各时段用电量 q(h)目标函数是“用电效用减去电费支出”。效用函数常用二次函数来近似比如 U(q)a·q-0.5·b·q²其中 a 反映用户对电力的基本需求b 反映用户对电量增加的边际效用递减程度。再叠加一个套餐价格 p(h)用户要最大化 U(q)-p(h)·q同时满足上下限约束0≤q(h)≤Q_max(h)以及全天的总电量需求约束 Σq(h)D。价格给定时用户问题是线性约束下的二次规划极值点满足 KKT 条件。KKT 的核心是一阶导数条件a-b·q(h)-p(h)-λ_lowλ_up0其中 λ 是上下限约束的对偶变量。外加互补松弛条件λ_low·q0、λ_up·(Q_max-q)0。这套条件等价于用户的“最优响应函数”。为什么要把用户问题转成 KKT 条件放进上层因为只有这样才能把两个决策者的问题合并成一个单层优化。否则你就得在外层写一个“猜测价格-求解用户问题-更新价格”的迭代也就是所谓的高斯-赛德尔迭代法但那种方法在价格耦合强的地方很容易振荡不收敛。这里有一个复现时容易犯的错用户问题的总电量约束不要轻易加。加了 D 的约束模型更接近刚性负荷但如果你同时做套餐切换用户可能在两个套餐之间来回倒腾电量反而让模型不稳定。我的做法是先不加总电量约束让套餐价格自然引导用户用电量算完再检查一天总量是否落在合理区间。2.2 互补条件与 big-M 线性化的具体操作KKT 条件里最麻烦的是互补松弛条件因为它是 0≤某变量⊥某表达式≥0 这种形式。常规非线性规划求解器处理这类条件很头疼最省事的办法是用 big-M 法把它线性化。以用户均衡出力 q(h) 和上限约束的对偶变量 λ_up(h) 为例。互补条件之一是 λ_up(h)≥0Q_max(h)-q(h)≥0且两者乘积为 0。引入两个 0-1 变量 z1(h)、z2(h) 和足够大的正数 M把乘积条件拆成四条约束0≤λ_up≤M·z10≤Q_max-q≤M·z2z1z2≤1。这样就把非线性乘积条件换成了线性不等式组。初衷很简单保证 λ 和松弛量不会同时为正。同理由 KKT 条件替换下层问题得到一个单层约束模型再用带整数变量的大型规划求解器求解。big-M 的参数选择要特别当心。M 不能太小否则会砍掉真实最优解也不能太大否则数值稳定性会崩。我的经验是先按用户最大边际效用和价格上限估算一个理论界比如 a 的上限 10价格上限 3那 λ_up 理论上不会超过 7M 取 20 左右足够。等你验证小算例跑通了再考虑放大到一个保守值。2.3 单层化之后的问题类型与求解边界把用户问题转成 KKT 并入上层会得到什么类型的问题这个问题含有 0-1 整数变量、连续价格变量、用户电量变量以及价格乘电量这类双线性项。多位变量叠加后本质上是一个混合整数非线性规划或者更精确地说是一个带均衡约束的数学规划业内常叫 MPEC。理论上这类问题求解复杂度很高但在小规模算例里完全可控。所谓小规模指的是用户类型数量不超过五个、套餐类型不超过四种、时段数固定为 24。这种规模下即使保留部分双线性项用分支定界加外逼近也能稳定收敛。我在复现时就控制在三用户类型、三种套餐、24 个时段的规模求解时间大概几分钟完全可接受。如果你一上来就试图模拟 50 类用户那大概率会陷入局部最优甚至半天不出结果。先小后大是这类模型的铁律。3. Matlab 实现代码怎么拆、求解器怎么选3.1 求解器选择与测试规模控制Matlab 环境下很多人第一反应是用 fmincon。fmincon 可以处理带非线性约束的问题但用户问题 KKT 化后含大量 0-1 整数变量fmincon 并不直接支持整数。所以我的建议是优先考虑 YALMIP 工具箱加商业求解器比如 CPLEX 或 Gurobi。YALMIP 负责建模求解器负责实际计算。YALMIP 能够识别问题的数学性质把模型交给对应求解器分支处理。下表是我在复现时对比过的情况。求解器组合能处理的问题类型稳定性使用建议YALMIP CPLEX / Gurobi混合整数线性规划、二次约束规划稳定适合中小规模第一选择YALMIP IPOPT无整数变量时的非线性规划对初值敏感用于剥离整数的对比实验MATLAB fmincon非线性规划不直接支持整数只能处理简化模型自定义迭代法双层各自独立求解容易振荡仅用于小规模验证如果实验室没有商业求解器授权退而求其次的做法是把套餐选择变量提前枚举出来也就是先固定套餐组合再去解连续优化问题最后遍历所有套餐组合取最优。这种枚举本质是把离散决策放到外层暴力破解对三四个套餐组合还能接受组合数再多就放弃。3.2 一个清晰的主从求解框架与关键代码骨架我最终采用的框架分成五层参数设置层、变量定义层、约束拼装层、目标函数层、结算层。主文件只负责组织这五步具体逻辑放进独立函数里。这样当你换套餐类型或者换市场结算规则时改一处就能同步生效。第一层要设置的参数包括时段数 T24、用户类型数 N、每类用户的最大负荷 Q_max、效用函数的 a 系数和 b 系数、峰平谷时段划分、日前市场出清价格、实时市场价格、套餐档位阈值。第二阶段用 YALMIP 定义变量比如零售套餐价格为连续变量 sdpvar套餐选择标志为 binvar。最后把用户 KKT 条件逐一作为约束加入模型。下面的骨架代码是我的实际写法结构很简短% main.m 的框架示意 T 24; Nuser 3; PriceDA load(da_price.mat); % 日前市场价格 % 定义上层变量 P_Fix sdpvar(1,1); % 固定套餐价格 P_TOU sdpvar(1,T); % 分时套餐各时段价格 P_Sto sdpvar(1,3); % 阶梯套餐三档价格 Q_DA sdpvar(1,T); % 日前购电量 q_user sdpvar(Nuser,T); % 用户的用电量 lambda_up sdpvar(Nuser,T); % 上限对偶变量 ... % 下界相关变量略 z binvar(Nuser,T,2); % big-M 标志变量 Constraints []; % 套餐选择约束 Constraints [Constraints, sum(Choice,1) 1]; % 用户 KKT 一阶条件 for i 1:Nuser for h 1:T Constraints [Constraints, ... a(i) - b(i)*q_user(i,h) - Price_to_user(i,h) ... - lambda_low(i,h) lambda_up(i,h) 0]; % 互补条件线性化 Constraints [Constraints, ... lambda_up(i,h) M*z(i,h,1), ... Qmax(i,h)-q_user(i,h) M*z(i,h,2), ... z(i,h,1)z(i,h,2) 1]; end end % 目标函数售电收益减去购电成本减去实时偏差成本 Objective - ( 零售收入(Price,q_user) ... - sum(PriceDA.*Q_DA) ... - sum(PriceRT.*abs(sum(q_user,1)-Q_DA)) ); % 求解 ops sdpsettings(solver,gurobi,verbose,1); optimize(Constraints, Objective, ops);真实项目里代码比这长得多但主线思路就是这三板斧定义变量、拼 KKT 约束、设置目标函数并用求解器求解。我特别提醒一点在 YALMIP 里做乘积项时要小心比如某套餐价格乘以某时段电量YALMIP 会自动引入双线性项此时要么保留非线性交给支持非线性解算器的商业求解器去做要么通过强对偶条件把双线性项消掉。我复现时更推荐保留双线性项但只在小规模下用因为强对偶处理要引入很多对偶变量约束数量显著增加代码调试难度更大。3.3 实时平衡市场的结算环节要怎么嵌入主从博弈求解前日和实时官方做法可以像主过程一样写紧凑的优化模块。第一步只优化日前决策和用户套餐得到一对计划值第二步固定套餐取值把实际用户响应与日前购电量的偏差作为实时购电需求用实时市场价格结算。这样做的好处是把问题拆成两段每段都是结构化问题求解难度大幅降低。很多复现笔记把这两步耦合在一起写结果非线性项四处爆炸是在给自己找麻烦。具体到我复现的过程实时阶段其实不需要重新调用主从博弈。你在第一阶段求出的最优套餐价格和用户在相应价格下的电量其实相互兼容实时阶段只需把偏差电价加上平衡容量约束比如充电电量与购电量偏差必须在 ±15% 以内否则会增加惩罚项。这个操作很简单但很能体现“多级市场”的含义日前是决策层实时是修正层两个层次用的价格机制不同决策变量也不同。写代码时我习惯用两个函数一个 solve_day_ahead()一个 settle_realtime()两个函数之间的数据传递只有套餐价格、日前购电量和用户响应电量接口清晰排查问题也方便。4. 复现中最容易翻车的几个地方4.1 互补松弛条件在整数变量配平上悄悄失效我在第一次跑通单层模型时总觉得结果不对劲后来发现是互补条件线性化出了问题。用户上限约束的互补条件线性化后未必能严格保证乘积为 0因为 big-M 只是一个充分逼近。尤其在套餐选择变量同时参与时某些组合会被错误激活导致用户电量越过边界或者对偶变量与电量同时为正。排查这个问题的办法很直接跑完模型后把所有变量的结果回代到原始 KKT 条件计算每一组的互补残差比如 abs(lambda_up .* (Qmax - q))再检查最大值是否小于一个容差。如果残差很大说明线性化条件没生效这时候大概率是 M 值过小或者个别约束缺少 z 变量。我那时一旦发现残差超过 1e-2就会把对应变量单独拉出来冻结其他变量固定成一个小线性规划看问题出在约束拼装还是变量定义上。4.2 效用函数参数设置导致结果失真用户效用函数的参数 a 和 b 是最容易出问题的地方。如果 a 设置过大用户对电量的偏好无穷大价格再高用户也硬顶着全耗电量满载套餐设计就失去意义如果 b 太小边际效用曲线过于平坦用户对价格极度敏感电价稍微波动用电量就从 0 跳上最大模型会出现强烈的非线性求解器优先陷入局部最优。我一开始用文献里的常用参数但套到自己算例后完全失真后来按“负荷率”反推参数让我某两个时段价格差 30%用户总电量偏移量控制在 5%~15%这个范围比较符合实际需求响应弹性。调整参数时你可以给每个用户类型设一个“基准用电量映射关系”然后用最小二乘拟合 a、b保证在某个基准价格下用户的电量自然落在 Q_max 的 60% 左右。这个方法比盲目调参靠谱得多也方便你出敏感性分析图。4.3 M 值埋下的隐形炸弹big-M 法最容易被忽略的就是 M 无量级。M 值处理得不好求解器会给你一个“最优解”但回代到原始问题发现约束被破坏或者干脆出现不可行。M 太小真实解被排除M 太大求解器在分支定界时对二进制度变量敏感度变化极其严重导致数值困难。我在某轮试验中M50 时模型秒出结果但残差巨大改成 M500 后模型直接不可行最后取 M100 才稳定下来。这个现象非常典型。更稳妥的做法是用变量上界自动计算 M。比如对于每个用户类型先估算用户最大可能电量 Q_max 和最大边际效用 a再推算出 λ 的可行上界给每类用户设置各自独立的 M而不是全模型共用一个保守大数。如果你的商业求解器支持 SOS1 约束也可以把互补条件写成 SOS1 形式绕开显式 M 值不过 YALMIP 下这种写法对模型规模的限制很敏感。我把 M 改成每类用户独立值之后求解时间和稳定性都明显改善这一点强烈建议复现时优先做。5. 算例搭建与结果验证5.1 用最小化算例自检模型复现实验的第一步我建议做一个手算可验的最简化算例检验模型是否真的实现了主从博弈关系。比如构建三类用户每类用户取一个代表性负荷三类用户用电规律不同居民型负荷晚高峰明显商务型负荷日间平缓工业型负荷深夜低谷。套餐只设计三类固定电价、分时电价、阶梯电价。时段数取一个缩短版本比如 8 个时段而不是 24 时段方便观察。这样模型规模小到可以手推部分结果调试起来非常顺。下面是我当时用的示意参数表用户类型最大负荷MW谷时段基准负荷峰时段基准负荷可选套餐偏好居民型2.00.41.8分时套餐商务型3.01.22.6固定或分时工业型4.03.21.0阶梯套餐表中数据是虚构算例不是真实数据集。调好参数后我先验证一件事当套餐全部给固定电价时模型应该退化成普通单层价格优化结果要能闭合。闭合之后再引入分时和阶梯套餐看用户的套餐选择是否随价格结构变化。这样一层一层加出问题能立刻定位。5.2 从结果里要读出主从均衡的收敛证据主从均衡成立的判断标准不是“收益最大”四个字而是双层目标在均衡处同时满足上层目标和用户侧的 KKT 条件同时成立。所以在结果分析里除了看售电商利润曲线我建议也输出两个辅助指标一个是用户侧 KKT 残差最大值另一个是套餐选择变量的取值稳定性。以我的算例结果为例在最优解处居民型用户全选分时套餐商务型用户选固定套餐工业型用户选阶梯套餐套餐选择没有出现反复摇摆的二进制非整数结果。另一个值得观察的现象是负荷平移。引入套餐前总负荷曲线在傍晚有一个明显尖峰引入分时套餐后尖峰时段的负荷大约降低低谷时段用电量上升。这就是主从博弈里用户跟随行为的直观体现。你可以用一句话说明用户跟随者通过改变用电习惯让上层售电商在实时平衡市场的偏差成本下降同时自己也节省了电费。这个结论是整篇结果的“故事主线”写论文时一定要把它讲清楚。5.3 结果对比的正确姿势基准对照与敏感性扫描复现成功后不能只有一个“漂亮结果”否则判断不出模型改进在哪里。标准的对照方法是设置三类基准无套餐单一电价基准、单一分时电价基准、全套餐组合优化方案。对比指标包括售电商总利润、用户平均购电成本、系统峰值负荷、峰谷差等。售电商利润上升多少、用户平均成本下降多少这两个数字是论文里最常看到的也是审稿人关注点多。我在自己的算例中得到了这样的趋势结果多元套餐组合方案比单一固定电价方案售电商利润提高约一成用户平均电费降低约几个百分点同时峰时段的最高负荷有所下降。数字本身不一定适用于你的算例但趋势只要符合经济直觉大概率建模是对的。如果出现套餐组合反而比单一固定电价利润低的情况先别急着改结论回去检查套餐价格是否被压得太低或者阶梯档位设置的阈值太高导致高价档永远触发不了要多做几组敏感性扫描。敏感性扫描建议重点扫三个维度日前与实时价格差、用户效用函数的弹性系数、峰谷时段划分。每扫一个维度记录目标函数值的变化范围和套餐选择的切换点。这些切换点正是论文中“套餐设计差异化”最有说服力的证据。6. 从这版代码继续延伸的三条路线6.1 随机规划与风险偏好前文描述的模型本质上假设日前和实时电价是已知的确定性参数但在真实市场里实时电价波动可能很大。往这个方向延伸最常见的做法是把日前决策升级成第一阶段实时偏差变量和电价不确定性放进第二阶段用场景法做随机规划。风险偏好可以用条件风险价值刻画在目标函数里加一个 CVaR 惩罚项控制最坏情况下的购电成本。这个扩展只需在现有框架上加一个场景循环Matlab 里用 YALMIP 天然支持多场景聚合代码改动量不大。6.2 多售电商与网络约束如果你的研究不再局限于单售电商而是多个售电商在同一批发市场里竞争那么主从博弈就要升级成多领导者-多跟随者的均衡模型。底层用户仍是最低层跟随者但上层售电商之间还要满足纳什均衡条件求解难度会大很多。另一种常见扩展是把零售市场从“无网络”假设变成带配电网潮流约束用户节点的电压和线路容量会反向影响套餐价格设计。这类模型最明显的变化是约束从线性等式变成非线性潮流等式和混合整数变量叠加后一套代码的复杂度会成倍上升这时候通常要引入二阶锥松弛或启发式算法。6.3 分布式计算与多智能体学习KKT 单层化模型在中小规模下很有效但用户数量涨到几百个后单层模型会变成一个庞然大物求解时间难以接受。一个实际可操作的替代方案是把主从问题拆回双层用多智能体强化学习逼近用户响应函数或者用分布式劈裂算法把每个用户问题独立开只在主问题处交换价格和电量信息。这个方法优势在于可扩展劣势在于收敛性没有共同理论保障。如果想在论文里做“大规模算例”这条延伸路径是最值得投入的但建议先把确定性小算例的做法吃透再去碰大规模多智能体算法。我个人的体会是这类 EI 复现项目的调试重心往往不是博弈论本身而是 KKT 线性化、M 选取和参数标定。把这些细节处理好代码能稳定跑完算例能讲出完整的故事整篇论文的主干就已经立住了。最后再分享一个实用技巧每次改动参数后把用户 KKT 残差和实时偏差成本一起输出到日志文件这样你排查问题时不用反复重新跑模型就能知道哪一类设置会让约束悄悄失效。
阅读完成 · 觉得有帮助?
咨询建站