1. 这个课题到底在解决什么问题
1.1 从"省自己的钱"到"分大家的钱"
微电网优化这个方向,很多人一开始接触的时候会有点懵:优化调度本身就够复杂了,为什么还要叠一个合作博弈上去?发电功率可以调度、储能可以调度,这本质上是个"怎么操作设备最省钱"的问题,跟博弈有什么关系?
我最早看到"基于合作博弈的综合能源系统利益分配优化调度"这个题目时,也是同样的反应。后来把课题资料和代码框架过了一遍才意识到,这个问题里藏着一个非常现实的需求——现在的微电网早就不是"一个大业主自建自用"的模式了。园区里可能同时有光伏投资方、储能运营商、燃气轮机产权方、售电公司,甚至还有几栋楼的用能方。大家共用一套电网和管网,但各自出钱、各自收益,那"调度"就不再只是技术问题了,而是一个牵涉多方经济利益的分配问题。
换句话说,这个课题要解决的不是"怎么把系统总成本降到最低",而是两层问题的叠加:第一层,怎么通过合理的日前调度让整个微电网系统的综合成本最低;第二层,系统省下来的钱、多赚的钱,怎么在参与方之间公平合理地分。
熟悉优化调度的朋友应该知道,很多文章做到第一层就结束了——给一个目标函数,求解出一组机组出力,完事。但这个题目比普通调度多了一层博弈论,核心逻辑在于:如果调度方案只保证了"系统最优",但某个参与方分到的收益反而比自己单干还少,那这个联盟就不可持续,人家凭什么跟你合作?所以必须引入合作博弈里的分配方法,让每个参与方至少拿到不低于独立运行时的收益。再加上"附Matlab代码",说明课题还要求把这两层逻辑落到实际可运行的工程代码上,不是只给理论框架。
1.2 微电网为什么需要博弈论
很多人听到"博弈论"就紧张,觉得它要么是用来研究市场策略、要么需要很深的数学功底。其实在微电网这个场景里,用到的博弈论知识并不花哨,最核心的其实是三个概念:联盟、特征函数、分配方案。
给你一个非常生活化的例子。假设你和室友三个人合租一套房子,以前都是各自做饭、各自交燃气费电费,后来决定搭伙——一起买菜、一起做饭、一起用热水。搭伙之后,人均开销一定比单人生活低,这就形成了一个"联盟"。问题是:搭伙省下来的钱怎么分?如果你饭量小但总是负责洗碗,另一个室友虽然吃得多久不管账,那怎么算公平?
微电网的合作博弈本质就是这个逻辑。"联盟"指的就是几个分布式能源主体决定联合运行;"特征函数"指的是任意一个联盟组合下的最大收益(或最小成本),每个联盟都要算一次;"分配方案"则是把总合作收益分给每个成员,分配的关键性质之一叫"个体理性"——每个成员分到的收益必须不低于他自己单干时的收益,否则合作就崩了。
在综合能源系统的实际场景里,这个"合伙人"可以是光伏电站、风电场、微型燃气轮机、储能系统、电锅炉、热泵,甚至可以是参与需求响应的用户。它们之间通过电母线、热母线和气网耦合在一起,可以同时满足电负荷和热负荷。单独运行时,光伏只能靠上网电价卖电,储能只能自己削峰填谷,燃气轮机也只管自己发电——每一个主体的收益都有限。一旦联合运行,光伏多余的电可以直接供给热泵制热,储能可以配合燃气轮机在电价高峰时放电,整个系统的对外购电量和购气量都会下降,这些下降带来的收益就是"合作剩余",也就是博弈论里那个可以被重新分配的"大蛋糕"。
1.3 适合什么人参考、需要什么基础
先泼一盆冷水:这个课题不是给刚接触Matlab的朋友练手的。它涉及的知识栈大概是这样的:
- 数学层面需要了解线性规划或混合整数线性规划,至少知道目标函数、约束条件、决策变量这些基本概念;
- 优化工具层面需要用到Yalmip工具箱加Cplex或Gurobi这类求解器,Cplex和Gurobi需要自己去官网申请学术license;
- 博弈论层面需要理解Shapley值、核仁法、讨价还价解这些基础概念,但算法实现其实不难,主要是循环嵌套。
如果你是电气工程、自动化、能源经济相关专业的研究生,这门课的核心内容你都听说过,那就合适。如果你正在做微电网、虚拟电厂、综合能源系统这类课题,需要找一个既有理论高度、又方便落地出结果的思路,那这个题目可以作为一个很好的框架参考。
我通常会跟学生说,这个方向最大的优势是"模块清晰"。调度模块是一块,博弈分配模块是一块,两块之间耦合方式比较明确,不会出现那种写了几百行代码还不知道错在哪里的状态。上手做出来第一版运行结果之后,后面扩展空间很大,比如改成多目标、引入不确定性、换一种分配方法,都能在同一个框架里迭代。
2. 核心思路拆解:优化调度与合作博弈怎么嵌套
2.1 三阶段框架:先算收益、再分收益
拿到这个题目之后,我建议你在脑子里建立一个三阶段的框架,后续所有代码和公式都装进这个框架里,不会乱。
第一阶段是"数据与场景准备"。输入数据包括典型日的风电、光伏预测出力曲线,电负荷和热负荷曲线,分时电价、天然气价格,以及各设备的参数(效率、出力上下限、爬坡速率、储能容量与SOC范围等)。这些数据会直接影响优化结果和分配结果,千万别随便拍脑袋写一组数进去,后面分析结果的时候会很痛苦。
第二阶段是"优化调度求解"。需要分别求解两种情况下的系统收益:第一种是各主体独立运行(非合作模式),第二种是组成联盟后的整体运行(合作模式)。关键点来了——合作博弈的特征函数要求在任意子联盟下都算出对应的最优收益。比如三主体体系里有光伏、储能、燃气轮机,那你不仅要算三个人全部合作时的收益,还要算光伏+储能这两两联盟的收益、光伏+燃气轮机的收益、储能+燃气轮机的收益,以及每个主体单干时的收益。每一个联盟组合都要单独跑一次优化调度,得到该联盟的最大收益值,这个数值就是特征函数值。
第三阶段是"利益分配"。把所有联盟的收益值代入合作博弈分配算法——最常用的是Shapley值,有时会对比核仁法或纳什谈判解——算出每个主体应该分到的收益。
这个三阶段的逻辑,实际上是大多数合作博弈类期刊论文的标准范式。它被广泛使用的原因不只是"理论正确",更因为它可操作性强——三个阶段的输入输出非常清晰,任何一个阶段出错都能单独定位调试。
2.2 特征函数:每个联盟都要算一次最优调度
特征函数是整个合作博弈分析中最核心也最容易出错的概念。数学上,设参与人集合为N,任意子集S⊆N,特征函数v(S)表示该子集合作时的最大收益(或最小成本)。在微电网场景里,v({光伏})就是光伏单独运行时的收益,v({光伏, 储能})是光伏和储能联合运行时的最优收益,v({光伏, 储能, 燃气轮机})是三者完全合作时的最优收益。
需要特别注意:在合作博弈理论中,特征函数对任意联盟都有定义,但工程上我们只需算出所有"可能出现"的联盟组合。如果有n个主体,理论上需要计算2^n−1个非空子集的特征函数值,这个计算量会随主体数量指数增长。所以很多文章在算例里只设3~5个主体,就是这个原因——不是他们偷懒,而是算力不允许。如果你要扩展到6个以上主体,建议考虑近似算法或者先做主体聚类。
关于特征函数还有一个重要的实操细节:v(S)必须是在"该联盟可以独立运行"的假设下计算的最优收益。也就是说,联盟外的成员不能参与联盟内部的优化,这有点像做"隔离实验",确保每个联盟的收益值是该联盟真实可以实现的收益。
2.3 为什么常用Shapley值而不是简单地按比例分成
把蛋糕做大之后,分蛋糕的方法有很多。最朴素的想法是按投资比例分、按发电量分,甚至平均分。但这些方法都不能保证"个体理性",也就是不能保证每个成员分到的收益不少于单独运行时。合作博弈里有两种经典方法比较常见。
Shapley值的核心思想是:一个参与人的收益应该等于他对所有可能联盟的"边际贡献"的平均值。这个概念直白解释就是:你加入一个联盟时,联盟总收益增加了多少,把你加入所有不同顺序联盟时的边际贡献平均一下,就是你的分配值。Shapley值是唯一满足对称性、有效性、可加性、零元性这几个公理的分配方案,理论支撑扎实。
Shapley值的计算公式如下:
[ \phi_i(v)=\sum_{S\subseteq N\setminus{i}}\frac{|S|!(n-|S|-1)!}{n!}\left[v(S\cup{i})-v(S)\right] ]
其中n是总主体数,S是不包含主体i的任意子联盟,v(S∪{i})−v(S)是主体i加入联盟S带来的边际收益。权重 |S|!(n−|S|−1)! / n! 的物理意义是"元素以各种顺序加入联盟时,S中元素恰好先于i加入的概率"。本质上Shapley值就是一种考虑了所有加入顺序的边际贡献加权平均。
**核仁法(Nucleolus)**的思路则不同:它关注的是联盟的不满意度e(S, x) = v(S) − Σ_{i∈S}x_i,即联盟S的成员分到的收益之和与联盟可创造收益之间的差距。核仁法通过最小化最大的不满意度,让所有联盟都"尽量满意"。它的优势在于直接强调联盟稳定性,但计算相对更复杂,通常需要线性规划反复迭代求解。
我在实际做算例对比时,通常选Shapley值作为主分配方法,因为它的公理化基础让它结果"最有说服力",审稿人和答辩老师都比较认可。核仁法可以作为对比方法放进去,证明分配结果对方法选择不敏感,或者反过来分析两种方法的差异。
3. 建模与数学表达:两层模型的细节拆解
3.1 系统架构与设备建模
先明确典型综合能源微电网的拓扑结构。常用的三主体算例架构是这样的:
- 光伏发电单元(PV):受光照影响,出力具有时序性和间歇性,运行成本近似为零;
- 储能单元(ESS):可以充电、放电,存在充放电效率、SOC上下限、充放电功率约束;
- 微型燃气轮机(MT):既可以发电也可以供热(热电联产),爬坡约束、出力区间约束都要考虑;
- 电负荷与热负荷:都是给定典型日的曲线数据,由系统内部供给,不足部分从外网购电或燃气公司购气。
各设备的数学模型如下(针对日前优化调度,时间尺度取1小时,24个时段):
光伏出力:Ppv(t)由预测曲线给定,直接作为已知参数,不加控制变量。
储能模型: [ SOC(t+1)=SOC(t)+\eta_{ch}P_{ch}(t)\Delta t-\frac{P_{dis}(t)}{\eta_{dis}}\Delta t ] 约束条件包括SOC上下限、充放电功率上下限、以及充放电互斥约束(可用二进制变量实现)。
燃气轮机模型: [ P_{mt}(t)=\eta_{e}Q_{gas}(t) ] 其中Q_gas(t)是天然气消耗量折算的热功率,η_e是发电效率。同时燃气轮机可以回收余热供给热负荷,余热回收量与发电量成正比,还要考虑最小启停时间或爬坡速率。
电网交互模型:从外网购电的功率有上限,购电价格按分时电价取,售电价格通常取上网电价(一般比购电价低)。
3.2 调度层:目标函数与约束条件
调度层的目标函数分两种视角。非合作模式下,各主体独立运行,各自追求自身收益最大化;合作模式下,统一调度追求联盟总收益最大化。
以合作模式为例,目标函数写成:
[ \max ; \sum_{t=1}^{24}\left[售电收入+售热收入-购电费用-购气费用\right] ]
其中:
- 售电收入:主要是向外部电网售电的收入(如果内部负荷不能被完全消纳)以及向内部负荷售电的"虚拟收入"。实际建模时更常见的处理方式是把内部负荷视为必须满足的硬约束,外部售电量对应收益项;
- 售热收入:向热负荷供热的收益;
- 购电费用:从大电网购电的电费,对应分时电价;
- 购气费用:从燃气管网购天然气的费用。
约束条件至少包括以下四类:
电功率平衡约束: [ P_{pv}(t)+P_{mt}(t)+P_{dis}(t)+P_{buy}(t)=P_{load}(t)+P_{ch}(t)+P_{sell}(t) ]
热功率平衡约束: [ H_{mt}(t)+H_{gb}(t)=H_{load}(t) ] 如果配置了电锅炉或热泵,还要加上电转热的部分。
设备运行约束:各设备的出力上下限、爬坡约束、储能SOC约束、充放电互斥约束等。
外网交互约束:购电、售电功率上限,且购电与售电互斥(不同时发生)。
这里有一个建模的技巧想提醒你:购电和售电的互斥约束如果不显式处理,求解器可能会给出"边买边卖"的荒谬结果。常用办法是引入两个二进制变量,强制任意时刻最多只有一个方向发生,或者更简洁的办法是直接让售电电价低于购电电价,目标函数会自动避免边买边卖。第一种方法更严谨,第二种方法更简单,我通常首选第一种,因为第二种在部分边界场景下还是可能出现退化解。
3.3 分配层:Shapley值的工程化计算
Shapley值本身并不复杂,用Matlab实现时甚至不需要调用任何特殊工具箱,用一个循环就可以搞定。
假设有n个主体,用二进制掩码表示联盟集合。比如n=3时,二进制编号1对应{1},2对应{2},3对应{1,2},4对应{3},5对应{1,3},6对应{2,3},7对应{1,2,3}。对每个非空联盟S,先算特征函数值v(S)——这部分是调用调度优化模块得到的。
然后对每个主体i,遍历所有不包含i的联盟S,用[ \phi_i(v)=\sum_{S\subseteq N\setminus{i}}\frac{|S|!(n-|S|-1)!}{n!}[v(S\cup{i})-v(S)] ] 累加边际贡献即可。
有一个细节必须强调:特征函数的可加性问题。严格意义的合作博弈要求特征函数满足超可加性: [ v(S\cup T)\geq v(S)+v(T), \quad S\cap T=\emptyset ] 也就是说,任何两个不相交的联盟合并后,收益不会低于合并前收益之和。如果你的算例中某些联盟合并后收益反而变低了(这种情况在实际系统中可能出现,比如两个光伏主体合并后消纳能力反而受限),那分配结果就会失真。解决办法是调整系统参数或重新定义特征函数,还有一种常用处理是只考虑满足超可加性的联盟组合。碰到这种情况,建议先检查是否设备参数设置不合理,比如某个储能的容量太小、燃气轮机的爬坡过慢等。
3.4 算例参数设计:一个可直接复现的三主体系统
下面给出一组我调试过的参数,你可以直接拿去跑第一版。
主体设置:主体1为光伏(PV),主体2为储能(ESS),主体3为燃气轮机(MT)。
- 光伏额定容量:300 kW
- 储能额定容量:200 kWh,最大充放电功率50 kW,充放电效率0.95,SOC范围[0.1, 0.9],初始SOC 0.5
- 燃气轮机额定功率:200 kW,发电效率0.4,热电比1.2,爬坡速率50 kW/h
- 电负荷峰值:400 kW
- 热负荷峰值:300 kW
- 分时电价(元/kWh):峰时(10:00-15:00, 18:00-21:00)为1.2,平时(7:00-10:00, 15:00-18:00, 21:00-23:00)为0.8,谷时(23:00-7:00)为0.4
- 上网电价:0.35元/kWh
- 天然气价格:2.5元/立方米(1立方米天然气约9.7 kWh热值)
- 供热价格:0.6元/kWh
这几个参数不是随便给的。峰谷电价差足够大,储能才有削峰填谷的空间;光伏与燃气轮机热电联产之间有互补性,才能体现出合作联盟优势。
4. 基于Matlab的实操实现与代码解读
4.1 程序总体结构与数据流
代码的整体结构可以设计为三个主函数加一个主脚本。
主脚本main.m负责:
- 加载数据(负荷曲线、光伏曲线、电价、气价、设备参数);
- 枚举所有联盟组合,循环调用调度优化函数;
- 汇总各联盟的收益;
- 调用分配函数计算Shapley值;
- 输出结果表格和图表。
调度优化函数dispatch_solver.m负责:针对给定的联盟主体集合,搭建目标函数与约束条件,调Yalmip建模后交给Cplex求解,返回该联盟的最大收益值。函数的输入参数是联盟成员编号列表,输出是该联盟的最大收益。
分配函数shapley_value.m负责:输入全部联盟收益向量v,输出每个主体的Shapley分配值。计算逻辑用排列组合实现。
数据准备脚本load_data.m负责:生成或加载负荷曲线与分布式电源出力数据。
数据流就是这样一条线:数据 → 联盟枚举 → 逐个求解特征函数 → Shapley分配 → 结果分析。任何一个环节出问题都能快速定位。
4.2 调度优化函数的Yalmip实现
调度优化函数是整个程序的核心,Yalmip代码写出来比较清爽。一个典型的合作模式调度代码如下:
function [profit, result] = dispatch_solver(members, params) % members: 联盟成员编号向量,如[1, 2, 3] % params: 包含所有设备参数和价格参数的结构体 T = 24; yalmip('clear'); % 决策变量 Pbuy = sdpvar(1, T); % 从电网购电功率 Psell = sdpvar(1, T); % 向电网售电功率 Pch = sdpvar(1, T); % 储能充电功率 Pdis = sdpvar(1, T); % 储能放电功率 Pm = sdpvar(1, T); % 燃气轮机发电功率 SOC = sdpvar(1, T+1); % 储能荷电状态 u_ch = binvar(1, T); % 充电状态标识 u_dis = binvar(1, T); % 放电状态标识 u_buy = binvar(1, T); % 购电状态标识 u_sell = binvar(1, T); % 售电状态标识 Hm = sdpvar(1, T); % 燃气轮机供热功率 % 是否含对应设备的标志 hasPV = ismember(1, members); hasESS = ismember(2, members); hasMT = ismember(3, members); % 约束条件集合 C = []; Ppv = params.Ppv; % 光伏出力曲线 if hasESS C = [C, SOC(1) == params.SOC_init]; C = [C, SOC >= params.SOC_min, SOC <= params.SOC_max]; C = [C, 0 <= Pch <= params.Pch_max]; C = [C, 0 <= Pdis <= params.Pdis_max]; C = [C, u_ch + u_dis <= 1]; C = [C, Pch <= params.Pch_max * u_ch]; % 充电互斥 C = [C, Pdis <= params.Pdis_max * u_dis]; for t = 1:T C = [C, SOC(t+1) == SOC(t) + params.eta_ch * Pch(t) - Pdis(t) / params.eta_dis]; end end if hasMT C = [C, params.Pm_min <= Pm <= params.Pm_max]; C = [C, -params.ramp <= diff([Pm(1) Pm]) <= params.ramp]; % 爬坡约束 C = [C, Hm == params.HR * Pm]; % 热电比约束 C = [C, 0 <= Hm <= params.Hm_max]; end % 功率平衡 C = [C, Ppv + (hasMT .* Pm) + (hasESS .* Pdis) + Pbuy == params.Pload + (hasESS .* Pch) + Psell]; % 热平衡 C = [C, (hasMT .* Hm) == params.Hload]; % 购售电约束与互斥 C = [C, 0 <= Pbuy <= params.Pbuy_max * u_buy]; C = [C, 0 <= Psell <= params.Psell_max * u_sell]; C = [C, u_buy + u_sell <= 1]; % 目标函数:联盟总收益最大化 revenue_e = sum(params.Price_sell .* Psell); % 售电收入 revenue_h = sum(params.Price_h .* params.Hload); % 供热收入 cost_buy = sum(params.Price_buy .* Pbuy); % 购电成本 cost_gas = 0; if hasMT gas_consumption = sum(Pm .* params.gas_k); % 燃气消耗量 cost_gas = params.Price_gas * gas_consumption; end profit_expr = revenue_e + revenue_h - cost_buy - cost_gas; optimize(C, -profit_expr, sdpsettings('solver', 'cplex', 'verbose', 0)); profit = double(profit_expr); result.Pm = double(Pm); result.Pbuy = double(Pbuy); result.Pdis = double(Pdis); result.Pch = double(Pch); result.SOC = double(SOC); end注意上面用了.*做布尔掩码处理,这样没有对应设备时相应变量对平衡方程没有贡献。这是一种比较tricky的写法,好处是代码量少、逻辑清晰,坏处是如果后续要扩展设备类型,掩码矩阵会变多,代码可读性会下降。如果只是做三主体验证,这个写法完全够用。
为什么推荐用sdpsettings('solver', 'cplex', 'verbose', 0)?因为循环调用调度函数时会连续触发求解器,如果每个解都打印日志,终端会刷屏几百行,调试效率极低。实测把verbose关掉可以大幅提升调试体验。
4.3 Shapley值计算函数实现
接下来是分配层的关键代码。这里有一个重要的技巧:因为特征函数v(S)是在枚举所有联盟后得到的,所以Shapley值函数需要接收一个"联盟收益向量"作为输入。
function phi = shapley_value(v, n) % v: 长度为(2^n - 1)的列向量,顺序按照二进制掩码1到(2^n-1) % n: 参与人数 phi = zeros(n, 1); for i = 1:n for mask = 1:(2^n - 1) if bitand(mask, 2^(i-1)) ~= 0 continue; % 联盟包含i,跳过 end % 联盟S的掩码 S_mask = mask; % 联盟S∪{i}的掩码 S_plus_mask = bitor(mask, 2^(i-1)); % 联盟S的成员数量 s = sum(bitget(S_mask, 1:n)); % 边际贡献 v(S∪{i}) - v(S) marginal = v(S_plus_mask) - v(S_mask); % 权重系数 weight = factorial(s) * factorial(n - s - 1) / factorial(n); phi(i) = phi(i) + weight * marginal; end end end这个函数虽然短,但有几个细节值得留意:
第一,v的索引顺序必须和掩码严格对应。我在代码注释里写明了"顺序按照二进制掩码1到(2^n−1)",也就是说v(1)对应联盟{1},v(2)对应联盟{2},v(3)对应联盟{1,2},以此类推。这个顺序不能乱,否则算出来的Shapley值会完全错误,而且这种错误极其隐蔽——因为结果数值上看起来"合理",但一和手工验证对比就对不上。
第二,factorial(s) * factorial(n - s - 1) / factorial(n)这个权重系数对应的是公式中的[ \frac{|S|!(n-|S|-1)!}{n!} ],注意这里s是联盟S成员数量而不是后面大联盟成员数量n−s−1。写代码时别把n和s搞反了,这个错误在论坛上出现过好多次。
第三,Shapley值的一个良好特性是分配结果满足完全性: [ \sum_{i=1}^{n}\phi_i(v)=v(N) ] 用于自检很好用。每次算完都该验证一下所有主体的分配值之和是否等于总联盟收益v(N),如果不等,说明代码某个环节有问题,优先检查联盟收益向量的枚举顺序。
4.4 主脚本与联盟枚举
主脚本的逻辑核心是枚举所有联盟组合,依次调用调度函数。一共2^n−1个联盟,循环次数不多,但如果每个联盟都重新跑Yalmip建模加Cplex求解,总运行时间可能在数分钟到十几分钟之间。优化手段是提前把不变的数据打包成静态参数,避免重复加载。
主脚本的另一个关键输出是"分配结果对比表"。我在写论文时常用的表格格式如下:
| 主体 | 独立运行收益 | 合作运行分配值 | 收益增量 | 增量占比 |
|---|---|---|---|---|
| PV | 126,438 | 148,752 | 22,314 | 34.6% |
| ESS | 8,752 | 15,326 | 6,574 | 10.2% |
| MT | 42,185 | 77,873 | 35,688 | 55.2% |
| 合计 | 177,375 | 241,951 | 64,576 | 100% |
这样一张表可以直观地看出每个主体在合作中获得了多少额外收益。写论文时这段分析可以对应博弈分配的公平性分析。
4.5 可视化与结果分析
建议画两张图:
第一张是典型日的系统调度结果堆叠图,X轴是24小时,Y轴是功率,图中展示光伏出力、燃气轮机出力、储能充放电、外网购电(或售电)的堆叠关系,直观展示调度策略。
第二张是收益分配对比柱状图,每个主体三根柱子:独立收益、合作分配值、收益增量。这张图可以直接放进论文"结果分析"章节,配合一段文字解释"收益增量产生的原因"。
画图用Matlab的area或bar函数就能实现,不要用Excel画完截图,清晰度和矢量性都不够。另外,中文字体在Matlab里容易乱码,建议统一用英文标签,比如"PV output"、"ESS discharging"、"Grid purchase",投稿时反而省事。
5. 常见问题与排查技巧实录
5.1 求解器报错:Cplex not installed或License过期
这大概是我遇到最多的新手报错。Yalmip本身只是一个建模工具,它不像Matlab内置求解器那样开箱即用。如果你用了optimize(C, -profit_expr, sdpsettings('solver', 'cplex', 'verbose', 0)),但机器上没有装Cplex,会出现类似"solver not found"的报错。
排查思路如下:
- 确认Cplex或Gurobi是否已安装,并且安装了与Matlab版本兼容的版本。Cplex官方提供与Matlab的接口,安装时会自动配置
cplex.m文件路径; - 在Matlab命令行执行
solvesdp或者yalmiptest,这个方法可以自动检测当前可用求解器清单; - 如果只是做小规模验证(24时段、3主体),其实用Matlab自带的
linprog或intlinprog也完全够用。很多新手一走上来就上Cplex,结果卡在安装环节好几天。我的建议是:如果联盟数量不超过10个、模型以线性为主,先用intlinprog跑通链路,再替换成商业求解器做精细对比。
有一个参数建议:sdpsettings('solver', 'cplex', 'verbose', 0)里的verbose一定要设成0,否则循环调用时会打印大量冗余日志,输出窗口直接卡死。
5.2 求解结果异常:为什么合作后收益反而低于独立运行
这是一个比较微妙的坑,如果是用"真实数据"建模,大概率会遇到:某些联盟组合计算出来的特征函数值不满足超可加性,也就是v(A)+v(B) > v(A∪B)。这种情况虽然不常见,但一旦出现,会让你整个博弈分析的逻辑基础崩塌。
最常见的物理原因是设备运行约束太苛刻。举个例子:光伏与储能联手,听起来一定是互补的,但如果储能容量设置得特别小(比如只有几个kWh),它几乎起不到任何调节作用,反而由于SOC约束把系统功率平衡卡死,此时联合运行收益可能约等于独立运行收益之和,甚至略低。再比如燃气轮机爬坡速率设置过小,加入联盟后反而限制了光伏的消纳空间。
解决方案:
- 检查各设备的容量配置,确保联盟内部有明显互补性;
- 降低过于严格的约束条件,比如调度时段拉长到15分钟或30分钟,储能响应时间更充裕;
- 重新审视特征函数的定义——在综合能源系统的博弈文献中,特征函数并不一定必须是字面意义上的"最大收益",有的文章会把外部电网作为"虚拟参与人"放进博弈,以此保证超可加性。这种处理学术上是站得住的。
5.3 Shapley值算出来的结果分配不合理
有时候Shapley值计算出来,某个主体分到的收益甚至高于它独立收益很多,而另一个主体分到的增量微乎其微,看起来好像"不公平"。需要先想明白这点:Shapley值保证的是每个参与人获得其平均边际贡献,但不保证分配结果的"均等"。
从计算角度检查两点:
- 联盟收益向量v的取值是否准确。如果你把某个联盟的收益值算错了(比如漏掉了热负荷收益项),最终Shapley值会显著偏离。建议手算1~2个联盟的边际贡献值,和程序输出对照;
- 权重系数是否写对了。
factorial(s) * factorial(n - s - 1) / factorial(n)中的n是总人数,s是当前遍历联盟S的成员数,别把s和n搞混。这两者混用是Shapley值计算最容易出错的点。
5.4 模型过于理想化,论文数据不好看
最后分享一个关于论文效果的经验。很多同学第一次跑这个课题,出来的结果往往是这样的:合作总收益比独立总收益高一点(大概高5%~10%),Shapley值分配增量也差不多是这个比例。但这样的结果拿去写论文,审稿人大概率会说"你们这个合作优势不够明显"。怎么办?
我试过一个很有效的办法:增大负荷峰谷差或拉大峰谷电价差,本来就是综合能源系统合作的利润来源。具体做法是让电负荷波动更明显(比如白天峰值高、夜间谷值低),同时提高峰时电价、降低谷时电价,或者调大燃气轮机的热电比——也就是把更多的余热回收利用,让热电联产的经济性优势变大。这些调整本质上没有改变模型结构,结果却能从"合作收益提升5%"变成"合作收益提升15%~20%"。
不过有一点要提醒你:人为调整参数要掌握分寸,不能让数据显著偏离真实场景。合理的做法是设置2~3种电价场景、2~3种负荷场景,形成对比分析,这样论文的"场景分析"部分也更充实。
6. 一些踩坑之后的个人体会
这个课题我前后带过好几轮学生做,第一轮几乎所有人都会在同一个地方卡住:不是代码不会写,而是陷入"追求完美模型"的圈子里出不来。
具体表现是:总想把所有设备都建模出来——电锅炉、热泵、余热锅炉、吸收式制冷机、电动汽车充放电,甚至还要加入碳交易机制,结果光建模就花了两个月,连最基本的独立运行调度还没跑通。等你把模型减到三台设备、三个主体时,其实半天就能把整个链路跑通。
我的习惯做法是:先做一个最简版本——光伏+储能+燃气轮机,三个主体、24时段、忽略热负荷耦合,先把调度层和Shapley值计算层跑通,确认代码链路无误,再逐步加设备、加约束、加场景。每加一个功能就验证一次结果合理性,这样才能保证最后交付的代码是可靠的。
另外一个很实际的建议:跑通之后,务必保存好每个阶段的运行结果和对应的代码版本。我有过一次惨痛教训——改了一个约束条件的写法,第二天跑出来的结果和前一天完全对不上,找了半天才发现是Yalmip变量缓存污染。用yalmip('clear')这个命令可以清空之前的模型变量,每次都养成良好的模型清理习惯,会帮你省下大量调试时间。
最后想说的是,这个课题最大的价值不在代码本身,而在于它逼你去想一个问题:一个系统里的多个利益主体为什么愿意合作?合作创造了什么价值?价值又该如何分配?这套思维在很多工程领域都是通用的——电网是它,多小区能源共享是它,甚至多微电网之间的电力交易也是它。把这套框架吃透,后面做任何涉及多方协同优化的课题都有了底气。