去年接了一个园区微网优化调度的项目,甲方需求很直接:运营方要能盈利,但电价不能定得太离谱,用户不能被赶跑,同时在光伏出力不确定的情况下还要避免极端亏损。翻了一圈文献和内部参考方案,真正把这几件事放在同一个框架里做的,就是“基于合作型Stackelberg博弈与条件风险价值的微网动态定价优化调度策略”这一套思路。这个策略的核心并不复杂:微网运营商作为博弈领导者先给出动态电价,用户作为跟随者根据电价调整用电计划,两边不是纯对抗关系,而是把社会福利这个总蛋糕做大并且分得相对公平;再用条件风险价值(CVaR)给不确定性上保险。这篇文章主要讲清楚几个问题:主从递阶模型怎么搭、CVaR怎么嵌进去、双层问题怎么求解、动态定价和调度怎么联动,以及我在实际项目里踩过的坑。适合正在做微网、园区综合能源、虚拟电厂方向的人参考,也适合要写论文或者做方案设计的研究生借鉴。
1. 先把标题“翻译”成大白话
1.1 微网动态定价与优化调度,到底在解决什么问题
微网规模不大,但五脏俱全:光伏、储能、微型燃气轮机、各类负荷、与上级电网的交换功率,运行约束条件和大电网完全不同量级。传统做法里,定价和调度经常分开做:运营方先根据负荷预测制定分时电价,再在固定电价下安排机组出力和储能充放电。但这两个环节相互影响很深:电价一变,用户负荷就变;负荷一变,最优调度方案就变;调度一变,边际成本和网络约束又反过来影响电价该怎么定。把它们割裂开,做出来的方案往往“看起来每步都合理,整体上却互相打架”。
这个标题把动态定价和优化调度并列,本质上要求两者联合决策。微网运营商在决策时,不仅要回答“储能几点充几点放、燃气轮机发多少电”,还要回答“每个时段电费定多少,才能让用户的响应结果正好配合我这边的运行计划”。这是一个典型的“先定价、再调度,但调度结果反作用于定价”的耦合问题,单靠人工经验和简单时序仿真很难处理,必须建模成优化问题整体求解。
1.2 Stackelberg博弈:谁先报价,谁跟着调整
可以用下棋来理解Stackelberg博弈:微网运营商是先手,先把电价报出去;用户是后手,看到电价之后再调整用电计划。这个模型也被称为主从递阶博弈。
标题里的关键词是“合作型”,这一点非常关键。很多文献用的是非合作型博弈,各方各自最大化自己的利益,最后在市场均衡点达到一个勉强稳定的结果。但实操中问题很明显,如果运营商只盯着自己的利润,把电价定得非常高,用户短期没得选只能忍着,长期一定会选择退出、自建屋顶光伏或者转向其他供应商,微网的运营基础就没了。所以合作型的处理方式是,在上层目标里把用户效用或者社会福利部分放进去,运营商在定价时主动兼顾用户剩余,双方都在一个可持续的合作区间里寻求解。我在项目里具体是把用户侧的综合满意度、转移负荷的补偿成本都打包进上层模型,相当于给定价加了一个约束框架,而不是单纯“价格宰客”。
1.3 CVaR条件风险价值:给不确定性买一份保险
光伏出力的随机性、负荷的波动以及现货市场电价的变化,这些不确定性如果只取期望值,做出来的调度方案往往在极端场景下非常脆弱。CVaR衡量的是收益分布里最差的那一部分的平均值,它不是预测,而是对风险的一种量化方式。
举例来说,某个方案在100个场景里可能有5个场景会亏损。VaR只看这5个场景对应的分位点数字,而CVaR会把这5个场景的平均亏损算出来,代表“真的发生小概率事件时,平均亏多少”。放在微网调度里,用CVaR做目标函数中的风险项,就相当于在利润最大化的同时,给极端亏损加了一个明确惩罚。它的好处不止是概念清晰,更重要的是它满足一致性风险度量的公理体系,数学上可以通过引入辅助变量转成线性约束,这直接决定后续能不能用商用求解器高效求解。这一点在第三章会详细展开。
2. 模型搭建的全过程:从目标函数到约束条件
2.1 主从递阶模型的总体架构
建模第一步,先明确谁是领导者、谁是跟随者,以及双方通过哪个变量交互。
我采用了两层结构。上层是微网运营商的决策,包括零售电价、与上级电网的交互功率、储能充放电功率以及微型燃气轮机的出力。下层是多个用户聚合体,每个聚合体接收电价信号后,在用电效用与电费支出之间做权衡,得到最优负荷曲线,再把结果反馈到上层。交互变量就是一天96个时段的电价p_t和对应时段的负荷P_L,t。
整个模型写成数学形式就是:上层求解自身目标函数的最优值,同时要求下层的负荷响应结果也包含在上层问题的可行解里。这就是一个典型的双层优化问题,不能直接丢进求解器,必须做等价转换。很多初学者在这第一步就容易遗漏一个关键点:下层问题本身也是一个完整的优化问题,有自己的目标函数和约束条件,而不是一个简单的函数关系。所以在建模时,下层用户的优化行为必须被完整地描述出来。
2.2 上层目标函数:利润、社会福利与风险成本的平衡
我用的上层目标函数分三块:运营利润、社会福利加权项、CVaR风险成本。
运营利润等于售电收入加上向上级电网卖电的收入,减去购电成本、燃气成本和储能运维成本。这块是运营方最关心的财务指标。社会福利加权项用用户效用减去用户购电支出来表示,再乘一个权重系数ω,这个ω可以理解为运营方对用户满意度的重视程度,也对应标题里的“合作型”特征。风险成本就是CVaR项,它对所有场景下的利润损失做尾部平均。
最终上层目标写成简化形式:
max (1 - ω) * Π + ω * SW - ρ * CVaR
其中Π是运营利润,SW是社会福利项,ρ是风险厌恶系数。这里有个明显的权衡关系:ω调大,价格会更温和,用户满意度高,但运营方利润空间变小;ρ调大,策略更保守,极端场景下的损失更小,但正常场景下的利润也会略降。这两个参数都不是技术参数,而是业务偏好的数学化表达,后面做方案对比时很有用。
2.3 下层用户模型:效用函数与价格响应
下层模型别做得太复杂,否则上层求解难度会成倍上升。我的做法是把用户看作一个理性聚合体,用一个二次凹效用函数U(P_L,t)描述用电带来的满意度,常见表达是U(P) = a * P - b * P²。给定电价p_t后,用户求解:max U(P_L,t) - p_t * P_L,t,约束条件是P_L,t的可调范围和爬坡限制。
由于目标函数是凹的,一阶最优性条件可以直接给出P_L,t关于p_t的线性表达式,这就是所谓的价格响应曲线。比较实用的是把可转移负荷和可中断负荷分开建模:可转移负荷允许在时间轴上平移,总用电量守恒;可中断负荷直接削减,但要带补偿成本。这两类负荷的价格弹性差异很大,分开建模能让动态定价策略更贴合实际情况。
2.4 CVaR的数学嵌入方式
这一步要小心。运行场景集从历史数据抽样生成,比如取200个光伏出力场景和负荷场景的组合,然后对每个场景i计算运营利润R_i,定义损失L_i = -R_i。
CVaR的计算可以写成:
CVaR = ξ + (1 / (N * (1 - β))) * Σ_i max(0, L_i - ξ)
其中ξ对应VaR阈值,β是置信水平,N是场景数量。这个式子里有max函数,直接放进目标函数是非光滑的。解决办法是引入辅助变量z_i ≥ L_i - ξ、z_i ≥ 0,把max项线性化。这样一来ξ本身也是决策变量,求解器会自动找到合适的分位点。
推荐用辅助变量法而不是先排序再截取尾部均值,原因是后者需要额外的排序逻辑和场景索引,在优化模型里极难实现,而辅助变量法可以直接嵌进线性规划框架里。这一步是整个模型能否用商用求解器高效求解的关键技术细节。
3. 从双层到单层:求解路径与工具选型
3.1 为什么“双层迭代”不能直接交差
很多初学者拿到这类问题的第一个想法是“上层定电价、下层算响应、再迭代循环直到收敛”。这个思路听起来合理,但有两个致命问题。
第一,下层响应是上层的函数,这种迭代本质上是逐次博弈。如果你不能证明映射是压缩映射,迭代可能发散,或者在两个解之间来回震荡,根本停不下来。第二,即便侥幸收敛了,得到的往往也不是真正的Stackelberg均衡,因为上层在迭代过程中只利用了下层当前的响应点,而没有利用下层的完整理性行为信息。
从工程稳定性和可证明性角度考虑,我更推荐的做法是:把下层问题用KKT条件完整替换,转成单层的带均衡约束的数学规划问题(MPEC),再通过线性化处理成MILP,交给商用求解器一次性求解。虽然模型规模会变大,但求解的确定性、全局收敛性都更可靠。这里补充说明一下,也有一些基于学习或启发式的方法能在特定条件下收敛,但在项目交付的场景下,我倾向于选择可解释性强、稳定性高的数学规划路线。
3.2 KKT条件与互补松弛的线性化
下层问题是凸二次规划时,KKT条件是最优性的充要条件。具体写出来包含三组:目标梯度条件、约束可行性条件、互补松弛条件。
梯度条件给出p_t与P_L,t之间的关系,这是上下层之间的桥梁。互补松弛条件形如λ * (P_max - P_L,t) = 0,其中λ是对偶变量。这里存在非线性乘积项,需要用Big-M法做离散化:对每一对互补变量,引入布尔变量b,写成:
λ ≤ M1 * (1 - b) P_max - P_L,t ≤ M2 * b
M1、M2的取值需要斟酌。取太小会强行截断可行解,导致求不出来最优解;取太大则会让数值问题变多,求解器容易在高斯消元过程中损失精度。我一般按模型中相关变量的上限的2到5倍来取,先跑出来再渐进缩小,这是排错的经验。
完成后,双层问题就变成带均衡约束的数学规划问题。如果再引入辅助变量处理双线性项,整个问题可以转成混合整数线性规划MILP,交由商用求解器处理。
3.3 商用求解器与工具链选择
我在这类问题上的选型很固定:Python + Pyomo + Gurobi。少数大规模场景会换CPLEX。原因是Pyomo建模灵活度高,写模型和跑场景比MATLAB + YALMIP更容易维护;Gurobi在MIQP/MILP上的求解速度和数值稳定性,实测下来比开源求解器好太多。
规模方面,数万变量的双层转化模型,商用求解器基本可以做到分钟内级别的求解,完全满足项目迭代需求。开源求解器可以做小规模模型上的教学验证,但项目交付我是不会用的,稳定性不够,时不时会出现数值警告,碰上复杂约束很容易直接提示不可行,排查起来非常头疼。
代码框架可以按下面的思路写:
import pyomo.environ as pyo # 定义时间集合 T、场景集合 S model = pyo.ConcreteModel() model.T = pyo.Set(initialize=range(96)) model.S = pyo.Set(initialize=range(200)) # 电价变量、负荷变量、储能SOC变量、辅助风险变量 model.price = pyo.Var(model.T, bounds=(price_min, price_max)) model.load = pyo.Var(model.T) model.soc = pyo.Var(model.T, bounds=(0, ess_capacity)) model.xi = pyo.Var() # VaR阈值 model.z = pyo.Var(model.S, bounds=(0, None)) # 尾部损失辅助变量 # 目标函数:利润 + 社会福利 - 风险系数 * CVaR model.obj = pyo.Objective( expr=profit_expr + omega * welfare_expr - rho * (model.xi + 1/(len(model.S)*(1-beta))) * sum(model.z[s] for s in model.S), sense=pyo.maximize ) # CVaR约束 + 下层KKT近似约束 model.cvar_con = pyo.Constraint(model.S, rule=cvar_constraint_rule) model.kkt_con = pyo.Constraint(model.T, rule=kkt_condition_rule) # 调用Gurobi求解 solver = pyo.SolverFactory('gurobi') results = solver.solve(model, tee=True)这个框架只是一个骨架,实际项目里还需要加上互补松弛的Big-M约束、SOC递推约束、与上级电网交互功率约束等。先跑通这个骨架,再逐步往下加细节,排错会顺畅很多。
4. 动态定价与调度策略的联动实操
4.1 时段划分与初始电价生成
别急着调参数,先把典型日算清楚。我用的是96点建模,也就是一天按15分钟一个时段。用历史负荷曲线做K-means聚类,选出夏季典型日、冬季典型日、过渡季典型日,在每个典型日里再按负荷水平和光伏净出力划分峰平台谷时段。
初始电价以电网购电成本为基准,峰时段乘2.0,平时段乘1.2,谷时段乘0.7,作为博弈的初始策略。注意动态定价不是每个时段孤立定的,还要考虑用户的价格弹性不能一步拉太大,否则下层响应会剧烈波动。我在实际项目里对相邻时段电价变化率加了限幅约束,要求不能超过15%,这个约束能让电价曲线更平滑,也避免用户看到跳崖式电价产生抵触心理。
4.2 储能充放电如何与电价联动
动态电价定下来之后,储能就是最大的套利工具。正常情况下,系统会在谷时段充电、峰时段放电,充放电功率要与SOC约束、功率爬坡约束一起放进上层。
实际操作中要特别注意SOC的初始值设定和日末SOC约束。我采用日末SOC与日初SOC一致的循环约束,这样仿真结果才能跨天复用。如果只从利润角度出发,储能会在峰时放电放到上限、在谷时充电充到上限,形成“两充两放”的运行曲线,这是正常现象。
但加入CVaR之后,储能策略会出现明显变化:在不确定性高的时段预留电量。比如光伏大发的上午场景,储能不会把电量完全放空,会留一部分应对午后的云层遮挡导致光伏出力骤降。这种“预留”从单日利润角度看是次优选择,从整个场景集的风险表现看却非常合理,这正是风险度量嵌入优化目标的价值所在。
4.3 仿真参数与结果维度
给出一套参考配置:光伏装机1.5MW,储能容量2MWh,充放电功率上限500kW,充放电效率0.95,微型燃气轮机额定容量800kW,天然气成本按单位热量折算,峰值负荷约1.2MW,用户聚合为居民和商业两类。
场景抽样方面取200个场景,置信度取0.95,风险厌恶系数从0到0.8变化。典型结果大致如下:
| 风险厌恶系数ρ | 正常运行日利润(相对值) | 5%最差场景平均利润(相对值) | 用户平均电费变化 | 社会福利综合指数 |
|---|---|---|---|---|
| 0 | 100% | -40% | 基准 | 较低 |
| 0.3 | 94% | -25% | 略增 | 中等 |
| 0.5 | 87% | -15% | 小幅增长 | 较高 |
| 0.8 | 79% | -8% | 明显增长 | 高 |
可以清楚看到,ρ从0调到0.5,正常运行日利润只下降约13%,但5%极端场景损失从正常利润的40%以内压到了15%以内。这就是拿CVaR做尾部风险管理的直观收益。如果只看期望利润,会觉得ρ越大越亏,但把尾部风险纳入评估后,风险厌恶策略的价值就非常明显了。
4.4 动态定价结果的具体呈现
做完仿真后,电价曲线通常呈现三个特征。第一,峰时段电价被推高,但不会高于用户自建光伏的平准化成本,否则用户会流失,这是合作型博弈模型的自然约束效果。第二,谷时段电价贴近甚至低于购电成本,运营商靠峰谷价差套利来补贴整体收益。第三,当光伏出力波动大的时段,电价会出现小幅上调,目的是抑制需求、减少购电风险敞口。
这些电价曲线的细节往往比总利润数字更有说服力。给甲方汇报时,把不同ρ值对应的电价曲线放在一起对比,对方马上能理解“风险偏好如何影响电价策略”这个抽象概念,比讲一堆数学公式有效得多。
5. 常见报错、坑点与排查实录
5.1 求解器报“不可行”怎么办
这个问题几乎每个人都会遇到。先不要急着改代码,先用求解器自带的不可行性分析功能找出冲突约束集合。Gurobi支持IIS(不可行性分析),会列出导致冲突的最小约束集合。
我遇到最多的是SOC约束和充放电功率约束同时松弛不够导致的问题。SOC上下限设为0到2MWh没有问题,但初值1MWh加上最大充电功率再除以效率、日末又要回到1MWh,在峰谷价差很大的电价场景下可能根本无法满足。解决办法是给SOC添加合理的运行范围,先不放日末SOC等于初始值的硬约束,跑出一版结果看SOC自然走向,再把日末约束放宽为“日末SOC与初始SOC偏差不超过0.2MWh”。
另一个常见原因是互补松弛中的Big-M取太小,导致有效可行域被错误截断。这种不可行问题通过IIS很难直接看出来,因为涉及的是3个以上约束的联合效应。排查思路是把Big-M统一放大5倍测试,如果从不可行变成可行,就是M值问题,需要重新标定。
5.2 风险系数和置信度怎么配
置信度β一般取0.95,这是学术和金融领域很常用的值,但工程上不能盲目套用。
如果实际光伏出力的极端场景特别明显,比如多云天气下出力从额定值骤降到20%,β取0.95意味着只看5%的最差场景,可能刚好覆盖这几次极端情况。如果数据里正常波动较多、极端尾部不厚,β取得过高会让模型对“罕见但影响不大”的场景过度反应,白白牺牲正常场景利润。
我的建议是项目初期用0.90,看到尾部风险度量结果稳定后再调到0.95。风险厌恶系数ρ则不是调参问题,而是业务偏好的数学化表达,可以在方案评审时给出几个系数的对比结果,让甲方选一个可接受的运行策略。这里不要替决策者做判断,要提供选择空间。
5.3 场景生成与数据泄漏问题
很多人做场景抽样只对光伏出力做随机抽样,忽略了负荷场景与光伏场景之间的相关性。光伏出力低时往往伴随负荷高,如果场景抽样是独立的,会合成出“光伏高且负荷低”的不真实场景,导致CVaR低估实际风险。
我处理方式是保留历史数据的联合分布:按小时从真实历史记录里取组合作为样本,或者用场景缩减算法把原始500个场景缩减到200个有代表性的场景。场景缩减可以使用同步回代算法,具体细节不展开,重点是要在缩减后对比一下缩减前后的分布差异,而不能只看样本数量。
这部分在项目里如果省略,仿真报告会非常漂亮,但实际部署的时候就容易出问题。我遇到过仿真结果很好、现场运行却出现意料之外亏损的情况,后来复盘发现就是场景生成环节忽略了相关性导致的。
5.4 双层模型的一种常见错误:把下层目标当成上层约束
看过几次别人写的代码,把下层的一阶条件简化成“用户一定按最大效用点运行”,然后把下层效用最大化直接设成上层约束。这种做法等于强行规定用户行为,不给用户任何自主优化空间,博弈结构就名存实亡了。
正确做法是把下层优化问题完整写出来,通过KKT条件或者强对偶理论把下层响应嵌入上层。如果下层模型规模太大导致求解困难,可以考虑把用户按类别聚合成少量聚合体,而不是逐个用户建模,这样能保留博弈结构同时控制计算规模。
针对这个错误,我自己总结了一个检查清单:看模型里是否存在“负荷变量既由上层决策又由下层决策直接设定”的情况,如果存在,说明博弈结构被破坏了。正常的处理应该是:上层设定电价,下层响应负荷,上下层之间只有一个电价变量和一个负荷变量作为接口。
5.5 数值稳定性:Big-M取值与求解器精度
最后一类坑来自数值稳定性。MILP求解器在求解大规模问题时,如果约束系数跨度太大,容易出现数值病态。比如变量范围从0.001到100000,跨越了8个数量级,求解器在做单纯形迭代时精度会崩溃。
解决方案有两个方向。一个是对变量做归一化,把功率、电量、价格等不同量纲的变量缩放到相近的数值范围。另一个是给Big-M和惩罚参数设置合理的边界,不要为了确保可行性把M值设成一个非常大的数。
我常用的做法是用变量的物理上限乘以一个2到5的系数作为M值。这样既能保证约束有效性,又能最大程度减小数值问题。如果求解器给出“Numerical trouble”警告,基本可以确定是M值过大或者约束系数跨度过大,需要回头检查建模方式。
6. 个人体会与后续扩展建议
这套策略在实际项目里给我最深的感受是,CVaR在初期阶段可能被当成“锦上添花”的数学技巧,但真正遇到一次光伏大波动引发亏损事件后,就知道这个尾部保险价值有多高。把它跟Stackelberg博弈放在一起,两件工具互相补位:博弈结构解决了“多方利益如何协调”的机制问题,CVaR解决了“未来不确定时怎么做不后悔的决策”的风险问题。
再分享一个小技巧:不要一上来直接写带风险项的完整模型。先把风险权重置零、只留单场景,把主从结构跑通,再一层一层加场景和风险项,排错会快很多。很多时候问题不是模型本身,而是场景数据、约束条件、离散化参数混在一起,出了错也定位不到源头。
这个策略后续的扩展方向很多。可以加入储能寿命衰减模型,让充放电策略更精细;可以把多微网之间通过共享储能进行交易谈判纳入博弈框架;也可以把电动车充电站作为一类特殊可调负荷接入,通过动态定价引导充电行为。这些都是同一套建模框架上可以延伸的方向,如果你正在做类似的微网项目,建议先把这篇文章里的基础框架吃透,再根据业务场景做扩展。