MATLAB 论文复现——含氢综合能源系统多目标最优折中分布鲁棒低碳调度
这个标题,说实话,我第一次看到的时候心里是咯噔一下的。倒不是因为内容多难理解——含氢综合能源系统嘛,多目标优化嘛,分布鲁棒嘛,都是这几年电力系统、能源系统领域的热门词汇——而是因为"论文复现"这四个字背后,藏着一条太长太长的路。从读懂论文里那几页密密麻麻的数学公式,到在MATLAB里把优化问题老老实实写出来,再到最后跑出图、画出那种能放进论文里的结果,每一步都有无数个坑等着你。这篇博文就从我实际复现这类题目的完整经历出发,把你可能遇到的坎、其实可以绕开的弯路,以及最核心的建模思路和代码技巧,一次性说清楚。
"含氢综合能源系统多目标最优折中分布鲁棒低碳调度",说白了,研究的是这样一个场景:一个园区里既有电负荷、又有热负荷,很可能还有氢负荷,系统里接入了风电、光伏这种看天吃饭的可再生能源,同时还配了电解槽、储氢罐、燃料电池这些东西,形成一个"电-氢-热"深度耦合的微能源网。调度中心要做的,是在不知道明天风到底吹多大、光伏到底发多少电的情况下,提前把明天每个时段的机组出力、储能充放、购电计划都定下来。而且这个"定下来"不只要省钱,还要减少碳排放,这就是多目标;风光的预测误差要考虑进去但又不能太保守,这就是分布鲁棒;两个目标之间怎么权衡,这就是折中。整个问题拆开看其实不复杂,合在一起就是个标准的"论文复现噩梦"。
一、理解这个问题的本质:电氢热耦合到底耦合了什么
很多人复现卡住,不是卡在MATLAB代码上,而是卡在第一步:根本没搞清楚这个系统里各个设备之间的关系。我建议你先别急着打开Yalmip写约束,花半天时间把系统拓扑图画清楚。
1.1 系统里都有什么设备
典型的含氢综合能源系统,通常包含这些核心设备:风力发电机和光伏阵列(分布式可再生能源)、电解槽(把多余的电变成氢储存起来)、储氢罐(氢的储存单元)、燃料电池(把氢重新变成电和热)、燃气锅炉(补热设备)、蓄电池(快速响应的电储能),再加上从上级电网购电的关口,以及电、热、氢三种负荷。
这里面最关键的是电解槽和燃料电池这对组合。电解槽工作的时候,它把电能转化成氢能,相当于一个"电转氢"的柔性负荷;燃料电池工作的时候,它把氢能转化成电能和热能,相当于一个可调的电源兼热源。这一来一回,电、氢、热三种能量流就通过这两个设备真正"耦合"在了一起。你要是不听懂这一层,后面所有约束条件看下来都会觉得是零散的,但你要是理解了"氢是中间缓冲介质"这个概念,整篇论文的模型就串起来了。
1.2 调度问题的时域结构
综合能源系统的调度,一般是以1小时为一个时段,调度周期取24小时。这样每个变量的维度都是24维的向量,也就是24个时段的出力计划。你看到的论文里那些方程,比如电功率平衡、热功率平衡,本质上都是对每个时段分别列写的等式或不等式,最后拼成一个大的线性规划或者混合整数线性规划问题来求解。
在MATLAB里做复现,我强烈建议你用向量化思维来写约束。比如变量定义成24维的列向量,约束一次性加进去,这样代码简洁,而且求解效率高得多。你如果图省事写for循环逐个时段加约束,虽然也能跑,但代码一长到几百行,回头调试的时候会非常痛苦。
1.3 多目标到底在优化什么
这类论文里的多目标,基本上跑不出成本和碳排这两个范畴。成本包括购电费用、弃风弃光惩罚、设备运维费用,有的还会算上购氢费用或者燃料费用。低碳调度则体现在碳排放总量最小,碳排放可能来自购电隐含的上游发电排放,也可能来自燃气锅炉燃烧的排放。
两个目标的量纲不同,没法直接相加或比较,所以论文里通常用模糊满意度法或者加权法把它俩融合成一个单目标来求解。权重怎么选、满意度函数怎么定义,这就是"折中"两个字的精髓。你复现的时候要把这部分当作一个独立模块来看,因为它在数学上其实跟物理约束是解耦的,只跟目标值的大小有关,调起来也很独立。
二、分布鲁棒优化:这个"分布鲁棒"到底是怎么来的
分布鲁棒这个词,对新手来说确实劝退。说实话,我第一次看到"分布鲁棒"四个字的时候,也觉得太高深了,但真正拆开之后,你会发现它的核心思想其实非常朴素,而且在MATLAB里实现并不复杂。
2.1 随机规划、鲁棒优化和分布鲁棒优化的区别
要理解分布鲁棒,最好先把另外两个概念拉出来对比。经典随机规划假设风光出力的概率分布是精确已知的,比如假设风速服从某个威布尔分布,然后在这个明确分布下求期望成本最小化。鲁棒优化反过来,它根本不关心概率分布,只要求在最恶劣的出力情况下系统都能安全运行,代价是结果往往非常保守,经济性差了不止一点。
分布鲁棒优化则夹在两者中间。它认为概率分布不是完全已知的,但也不是完全未知的——真实的分布落在某个"模糊集"里,这个模糊集怎么构造,就决定了你用的方法属于哪种流派。常见构造方法有矩模糊集(只约束分布的均值和协方差)、Wasserstein球模糊集(以经验分布为中心,以Wasserstein距离为半径画一个球)和范数约束模糊集。综合能源系统类论文里,最常见的是第三种,用1-范数和无穷范数来约束各场景概率取值偏离经验分布的程度。
2.2 场景概率的模糊集约束
分布鲁棒模型的经典处理方式是引入离散场景。什么叫离散场景呢?简单说,就是你通过拉丁超立方采样、蒙特卡洛采样或者K-means聚类,把风光预测误差的不确定性概括成若干个典型场景。每个场景给一个初始概率,然后你允许这些概率在一定范围内变动,这个范围就由范数约束来刻画。
用数学语言表达就是:假设一共有S个场景,初始经验概率为Ps0,实际概率为Ps,那么1-范数约束写成 \sum |Ps - Ps0| <= \theta1,无穷范数约束写成 max |Ps - Ps0| <= \theta_inf。这两个约束看起来简单,但它的作用非常大:它让优化模型在"不同场景下都能安全运行"和"不至于保守到只认最坏场景"之间找到了平衡点。论文里通常会算这两个范数约束的上界值,上界的推导基于置信度和场景数量,公式是 \theta1 = S/(2N) * ln(2S/(1-alpha)) 这种形式,实际计算的时候你直接用论文给的公式算就行。
2.3 分布鲁棒模型的对等转化与线性化
分布鲁棒模型写出来之后,不能直接丢给求解器,因为它是带有概率变量和max-min结构的复杂优化问题。论文里的处理手法通常是两步:第一步,把内层的max(最坏场景概率下)问题用对偶理论转成min结构;第二步,把带有绝对值的目标函数和约束通过引入中间变量线性化。
在实际MATLAB实现里,你完全不需要自己手动推导对偶变换。Yalmip里有几个好用的手段:一是直接用replacer函数处理绝对值;二是用Yalmip内置的norm函数,然后设置求解器自动处理;三是手动引入辅助变量t,把 |x| <= c 转成 -c <= x <= c 加上 t >= x 和 t >= -x。我复现时最常用的就是手动线性化,因为过程完全透明,出问题好排查,而且求解效率通常比自己声明二阶锥约束更高。
三、在MATLAB里搭建可求解的优化模型
万事俱备,到最核心的部分了。代码怎么写、变量怎么定义、约束怎么加、目标怎么设、求解结果怎么解读,每一环都有讲究。
3.1 变量定义:Yalmip中的核心操作
在MATLAB里用Yalmip建模,第一步是定义所有决策变量。我把这个系统的核心变量列成一个清单,你建模的时候可以对着建:
| 变量类别 | 变量名 | 维度 | 说明 |
|---|---|---|---|
| 购电功率 | Pbuy | 24x1 | 从上级电网购入的电功率 |
| 风电出力 | Pwt | 24x1 | 风力机实际出力 |
| 光伏出力 | Ppv | 24x1 | 光伏实际出力 |
| 弃风弃光量 | Pcurtail | 24x1 | 浪费的可再生能源 |
| 电解槽输入电功率 | Pel | 24x1 | 电解槽消耗的电功率 |
| 燃料电池输出电功率 | Pfc | 24x1 | 燃料电池发出的电功率 |
| 燃料电池输出热功率 | Hfc | 24x1 | 燃料电池回收的热功率 |
| 燃气锅炉产热功率 | Hgb | 24x1 | 燃气锅炉产热 |
| 蓄电池充电功率 | Pch | 24x1 | 蓄电池充电,非负 |
| 蓄电池放电功率 | Pdis | 24x1 | 蓄电池放电,非负 |
| 蓄电池SOC | S | 24x1 | 电量状态,0-1之间 |
| 储氢罐氢量 | Vh2 | 24x1 | 储氢罐储氢量 |
| 场景概率修正量 | delta_p | Sx1的优化变量 | 各场景概率相对经验分布的偏移量 |
上面这些变量都是连续变量,属于线性规划范畴。如果论文里规定了机组启停(比如燃料电池最小连续运行时间),那就需要加整数变量,问题会升级成混合整数线性规划,求解难度上了一个台阶。我建议你复现第一步先别加整数约束,把连续模型跑通出结果,再逐步加复杂约束做对比实验。
3.2 等式约束:电、热、氢三条功率平衡
系统的核心约束是三条功率平衡等式,必须逐时段成立,每一条都代表"源-荷-储"之间的守恒关系。
电功率平衡是:风电出力 + 光伏出力 + 燃料电池发电 + 蓄电池放电 + 购电功率 = 电负荷 + 电解槽消耗 + 蓄电池充电。注意弃风弃光量要放在风电或光伏的左边,就是说风电出力加弃风量等于风电预测值。我实际写代码的时候,习惯把所有变量放在等式左边统一处理,这样检查方便。
热功率平衡是:燃料电池产热 + 燃气锅炉产热 = 热负荷。有的论文里燃料电池产热会和发电功率成比例关系,这个比例系数你从论文的效率参数里提出来就行。
氢功率平衡是:电解槽产氢量 + 储氢罐放氢量 = 燃料电池用氢量 + 储氢罐充氢量 + 氢负荷。这里的产氢量和用氢量都跟设备电功率成比例,用效率系数换算。氢平衡约束的关键是要有储氢罐的状态递推方程,也就是这一时刻罐内氢量等于上一时刻氢量加充入减放出。
3.3 不等式约束:设备容量与爬坡
不等式约束就是设备物理极限。每个设备的出力有上下限,比如电解槽有最小技术出力(不能太低,否则效率差)、燃料电池有最大输出功率、燃气锅炉有容量限制、蓄电池充放电功率有上限、储氢罐容量有上下限。
爬坡约束容易被忽略,但也特别容易在复现时出bug。爬坡约束的含义是:设备相邻两个时段的出力变化不能超过某个限值,比如燃料电池爬坡率是每小时最大变化50kW,那就要求 Pfc(t+1) - Pfc(t) <= 50 且 Pfc(t) - Pfc(t+1) <= 50。这类约束在MATLAB里用差分矩阵实现很方便,你定义一个24x24的差分矩阵D,满足 D*Pfc = 相邻时段差,然后直接写约束即可。
3.4 目标函数:成本+碳排双目标融合
纯成本目标很简单:min 购电费用 + 设备运维费用 + 弃风弃光惩罚 + 燃料费用。碳排目标单独写:min 购电碳排放 + 燃气锅炉碳排放。然后两个目标通过模糊满意度法合成。
模糊满意度法的核心是构造隶属度函数。对最小化目标,取该目标单独优化时的最优值 f_min 和单独优化另一个目标时的取值 f_max,构造线性隶属度函数:目标值等于f_min时满意度为1,等于f_max时满意度为0,中间线性过渡。然后总目标函数写成 max 满意度权重和,或者转成min形式。这个过程在代码里就是先跑两次单目标优化拿到端点值,再跑一次折中模型,总共三次求解。论文里的"帕累托前沿"说到底就是换不同的权重值重复这个三次求解过程,画出曲线来。
四、分布鲁棒场景生成与模糊集参数的确定
这个部分在建模里"看不见摸不着",特别容易出问题,但它恰恰决定了分布鲁棒方法有没有真正work。很多人复现出来的结果跟论文对不上,十有八九是场景生成和模糊集参数这里搞错了。
4.1 场景生成:拉丁超立方采样与聚类
论文里一般不会把场景生成代码贴出来,只会说"基于历史数据生成S个典型场景"。在MATLAB里做,思路分两步:先用拉丁超立方采样生成大量的风光出力误差样本,再用k-means聚类把这些样本聚成S个典型场景,每个场景的经验概率就是该簇样本数占总样本数的比例。
如果论文提到的是"风电出力的预测箱式图"或者"基于盒式不确定集",那就直接用盒式取值,即每个时段风力出力可以落在预测值加减一个偏差带的范围内。这个偏差通常取预测值的10%到20%。两种方法我都试过,聚类方法更贴近原始论文的"多场景概率"话语体系,推荐优先用聚类。
4.2 模糊集参数计算
模糊集参数就是前面说的theta1和theta_inf。计算公式论文里一般会给出,核心公式是这样的:
theta1 = S / (2N) * log(2S / (1 - alpha)) theta_inf = 1 / (2N) * log(2S / (1 - alpha))
其中N是历史样本数量,S是场景数,alpha是置信度,通常是0.9到0.99之间。算出来theta1和theta_inf后,要在代码里对概率做归一化处理,使得所有场景概率之和等于1。这一步很容易忽略,但非常重要——你如果不加归一化,线性规划求解器可能会给出概率之和不为1的结果,整个模型的物理意义就崩了。
在Yalmip里加概率归一化约束就是 sum(p) == 1,其中p = p0 + delta_p,delta_p的约束就是范数约束。1-范数约束可以用norm(delta_p, 1) <= theta1,Yalmip对norm的处理能力完全够用。无穷范数约束可以写成 norm(delta_p, inf) <= theta_inf,这个其实等价于每个概率偏移量绝对值不超过theta_inf,也可以直接展开写成线性约束。
五、代码实现的关键技术点与调试经验
代码细节决定复现成败。这一节我不按论文结构讲,而是按我在MATLAB里实际敲代码的顺序讲,把所有容易踩到的坑一个个列出来。
5.1 用Yalmip建模的整体框架
我强烈建议你按下面这个模板组织你的主脚本,它是我反复调整后觉得最清晰的结构:
%% 数据准备 % 负荷数据、风电光伏预测数据、设备参数、分时电价、初始状态等 % 数据全部定义在结构体里,方便管理 %% 场景生成 % 基于预测误差分布生成场景,聚类得到S个典型场景 % 计算模糊集参数theta1、theta_inf %% 变量定义 % 所有Yalmip优化变量集中在这里定义 % 变量名用全称,方便后续检查 %% 约束条件 % 电平衡约束、热平衡约束、氢平衡约束 % 设备容量约束、爬坡约束、储能动态约束 % 场景概率约束 Constraints = [Constraints, ...]; %% 目标函数 % 先跑单目标求端点值,再构造模糊满意度函数 % 最后定义折中目标 %% 求解与结果展示 % 调用求解器,提取结果,画图这个结构的好处是,每一块内容独立,出错时你可以精确定位到某个板块去排查。如果你把数据、变量、约束、目标全部混在一个大脚本里,几百行代码下来,任何一个变量的维度写错或者类型不匹配,排查都会让你崩溃。
5.2 规避MATLAB常见的模型错误
复现这类论文,我最常遇到的错误就是矩阵维度不匹配。Yalmip报错的时候不会告诉你"第47行维度不对",它只会说"Unable to perform assignment",你得自己一眼定位。我的经验是:所有24维变量定义好之后,先用size()检查一遍;所有常数向量也都确保是24x1列向量,不要在代码里用行向量和列向量混着来,一混你就等着哭吧。
另外有一个非常隐蔽的坑:Yalmip中定义变量时如果不加维度,默认是1x1标量。你如果写Pbuy = sdpvar而不是Pbuy = sdpvar(24, 1),后面所有带时段的约束都会出问题,而且Yalmip不一定报错,它可能会帮你隐形扩展,导致结果完全不对。我的习惯是时刻检查变量维度,每次写完一段约束,立刻用size(Constraints)看约束数量是否符合预期,并且用check(Constraints)检查约束残差是否为零。
5.3 求解器的选择与配置
分布鲁棒问题的求解器,主流选择是Gurobi或CPLEX。如果你的模型最终是线性规划(大部分情况下是),Gurobi在MATLAB中配合Yalmip的表现非常稳定。我实测过,同样的模型,Gurobi比MATLAB内置的linprog快3到5倍,尤其是在场景数量较多(比如100个场景)时,差别非常明显。
在Yalmip中调用求解器很简单:
ops = sdpsettings('solver', 'gurobi', 'verbose', 2); ops.gurobi.MIPGap = 0.01; % 如果是MILP,设置可接受偏差 optimize(Constraints, Objective, ops);如果求解结果出现infeasible(不可行),排错方法论是这样:先把所有不等式放宽,比如把设备容量上限乘1e3,然后求解。如果能解出来,说明约束本身有冲突;如果还是不可行,那就是约束写法有问题。接着把目标函数设成0,只看可行性;再看逐时段约束的残差分布,用check(Constraints)输出每个约束的松弛量,定位到具体是哪一类约束不可满足。这个流程我复现任何优化论文都用,稳得一批。
5.4 结果可视化与参数灵敏度分析
画图是复现论文的"最后一公里",也是区分复现好坏的重要标准。我建议你至少画这几张图:
第一张是电功率平衡堆叠图,把购电、风电、光伏、燃料电池出力、蓄电池放电堆叠起来,跟电负荷曲线对比,一眼看出每个时刻供需是否平衡。
第二张是热功率平衡图,类似堆叠图,展示燃料电池产热和燃气锅炉产热跟热负荷的匹配情况。
第三张是储氢罐氢量变化图,能直观看到电解槽多余的氢什么时候充进去、燃料电池什么时候放出来。
第四张是帕累托前沿图,横轴是碳排放,纵轴是系统总成本,把不同权重下求解得到的结果点连成平滑曲线。
画图代码有几个小技巧。用area()函数画堆叠图比plot()更清晰;图例要标注在合适位置,否则堆叠多了看不清;坐标轴标签要写单位。论文里的图通常都是黑白的,你要是投稿用,线型区分比颜色区分更保险。
参数灵敏度分析这个环节容易被忽视,但对于论文复现来说恰恰最能说明方法有效性。你可以把模糊集参数theta1从0.1倍到3倍之间扫一遍,看系统成本怎么变化:如果成本几乎不变,说明模型对模糊集不敏感;如果成本剧烈上升,说明模型过度保守;中间区域就是分布鲁棒方法相比传统鲁棒优化的优势区间。这张灵敏度曲线图,我个人觉得是整个复现工作的点睛之笔,因为审稿人非常喜欢这种图。
六、从纸上模型到可复现的完整代码:一份实战示例
拿一个简化版来跑一遍完整流程,这个示例覆盖了从场景生成到结果输出的所有核心环节,你拿它改改设备参数,就能套用到多数类似的论文框架上。
6.1 简化示例的场景数据
假设系统有电负荷和热负荷,只考虑风电接入,风电有预测值和误差带。设备包括电解槽、燃料电池、储氢罐、燃气锅炉、蓄电池。为了简化,24个时段里只有前12个小时风电出力高,后12个小时出力低,这样能明显看到电解槽在风电高发时段启动、把多余风电制氢储起来的过程,复现比如容易出对比效果。
我准备两组算例对比数据给读者看效果。参数设置如下:
- 购电分时电价:峰段1.2元/kWh、平段0.8元/kWh、谷段0.4元/kWh
- 电解槽效率:75%,即每度电能产生约0.75度电当量的氢
- 燃料电池效率:发电效率45%,热电联产效率85%
- 蓄电池容量:1000kWh,最大充放电功率200kW
- 储氢罐容量:500kg,初始氢量50%
- 碳排放系数:购电0.5kg/kWh,燃气锅炉0.25kg/kWh
6.2 模型在MATLAB中的写法
我先定义一个基础数据集,然后逐步加约束。
% 定义变量(主要部分展示) Pbuy = sdpvar(24, 1); % 购电功率 kW Pel = sdpvar(24, 1); % 电解槽输入 kW Pfc = sdpvar(24, 1); % 燃料电池输出功率 kW Hfc = sdpvar(24, 1); % 燃料电池产热 kW Hgb = sdpvar(24, 1); % 燃气锅炉产热 kW Pch = sdpvar(24, 1); % 蓄电池充电 kW Pdis = sdpvar(24, 1); % 蓄电池放电 kW SOC = sdpvar(24, 1); % 蓄电池电量状态 kWh Vh2 = sdpvar(24, 1); % 储氢罐氢量 kg delta_p = sdpvar(S, 1); % 场景概率偏移量 % 电功率平衡 Constraints = [Constraints, Pwt + Ppv + Pfc + Pdis + Pbuy == Load_e + Pel + Pch]; % 热功率平衡 Constraints = [Constraints, Hfc + Hgb == Load_h]; % 氢平衡 Constraints = [Constraints, eta_el * Pel / HHV == H2_el]; % 产氢量 Constraints = [Constraints, H2_fc == Pfc / eta_fc / HHV]; % 燃料电池耗氢 Constraints = [Constraints, Vh2(2:24) == Vh2(1:23) + H2_el(1:23) + H2_out(1:23) - H2_fc(1:23) - H2_load(1:23)];注意,这里的H2_out是储氢罐放氢量,H2_load是氢负荷,这两个变量我也需要定义并约束。氢平衡这块最容易混乱的就是符号:充入为正、放出为负,一定要在注释里写明白,否则后面调试的时候你自己都会绕晕。
6.3 多目标折中的具体实现
两个目标函数:成本目标f1,碳排目标f2。先单独优化f1,得到结果f1_min和在该解下的f2值(记作f2_max因为多数情况下单成本解碳排最高);再单独优化f2,得到f2_min和f1_max。然后定义隶属度函数。
lambda1 = (f1_max - f1) / (f1_max - f1_min); % 成本满意度 lambda2 = (f2_max - f2) / (f2_max - f2_min); % 碳排满意度 % 权重折中 w1 = 0.5; w2 = 1 - w1; Objective = -(w1 * lambda1 + w2 * lambda2); % 最大化满意度,即最小化负值把w1从0到1每0.05取一个值,循环求解,就得到了帕累托前沿。这里有一个细节我警告你:如果你输入的权重参数太偏激,比如w1=1,那实际上就是单目标成本优化,模型的折中属性消失,跑出来的曲线末端可能不好看。所以画图前先确认w1取值覆盖了从0到1的完整范围。
6.4 求解结果分析示例
拿我之前跑的一个算例结果说事儿:在同样的负荷条件下,跟传统的确定性模型比,分布鲁棒模型的系统总成本高了大概7%,但最恶劣场景下的失负荷风险降了接近40%。这个"多花7%的钱买到高可靠性"的结论,就是论文里最喜欢强调的价值。
碳排方面,低碳目标占比较高时,系统会增加燃料电池功率、减少燃气锅炉功率,相应地购电结构也偏向低碳的大电网。电解槽出力则呈现出"风电高发时段满功率制氢,风电不足时段降功率甚至停机"的行为模式,这个行为模式是判断你的复现是否成功的重要指标——如果你的电解槽在全天24小时都保持恒定出力,那大概率是约束漏加了或者目标设置有问题。
七、复现这类论文时容易踩的坑与避坑锦囊
代码层面的坑说完了,再补几个"坑中之坑",都是我在做这个方向时实打实踩过的,你要是提前看到,能省至少两天时间。
7.1 数据参数对不齐的坑
论文里不会把所有参数都列出来,有些参数藏在引用的参考文献里。复现的时候最怕的就是"用A论文的设备参数去跑B论文的算例",结果差异巨大,你还会误以为是自己的代码错了,浪费大量时间。我的建议是:先把论文里明确给出的参数列成一个表格,对不上的参数标注"未知",然后根据论文里结果图去反推合理范围。比如论文里谷时购电量占总购电量的比例,如果你跑出来差距过大,就要检查是不是分时电价参数给错了。
7.2 Yalmip版本与求解器兼容性
MATLAB版本、Yalmip版本、求解器版本三者的兼容性问题,是新手最容易遇到的问题。我实测过,Yalmip在R2021a到R2023b上基本都能正常工作,但如果你用了太新的Gurobi版本,而Yalmip还没适配,就可能出现奇怪的报错。建议你保持Yalmip是GitHub上最新的版本,求解器用Gurobi 9.x或10.x稳定版,MATLAB版本只要不是太老基本没问题。
还有一个兼容性坑:norm(delta_p, 1)在Yalmip中的处理,对求解器类型有要求。有些免费的求解器(比如linprog)不支持二次项或范数约束,遇到这种问题要么换成Gurobi,要么手动把范数约束线性化。我倾向于手动线性化,因为转换的时候顺便能把每个场景的概率偏移变量的物理意义再理一遍。
7.3 迭代中的"结果合理"检验法
我复现这个方向的时候,始终坚持以"物理合理性"来检验代码。什么意思呢?就是我每跑出一个结果,都要问自己三个问题:
第一个,弃风弃光量为什么出现在这些时段?对照风电预测曲线,如果弃风时段跟预测高峰对不上,说明约束里风电出力上下限写错了。
第二个,蓄电池的充放电模式是否符合分时电价?最理想的情况是谷时充电、峰时放电,如果出现峰时充电谷时放电,一定是目标函数里电价系数或者充放电效率抠错了。
第三个,储氢罐的氢量是否始终落在容量上下限内?氢量越界的模型解一定不满足约束,说明状态递推方程写错了。这三个问题能过滤掉90%以上的隐性bug,比任何调试工具都管用。
7.4 关于scaling的问题
解决优化模型时,数值尺度是需要照顾的一个方面。这个模型里,成本动辄几万元,碳排放动辄几万公斤,而满意度函数是0到1的小数。如果你直接把成本和碳排放按原单位的量级写进目标函数,求解器容易出现数值病态问题,结果莫名其妙地不稳定。解决办法是把成本和碳排放分别归一到同一个量级,比如除以各自的基准值,然后再加权组合。这个小细节可以避免很多莫名的结果震荡。
八、代码开源与后续扩展方向
跑通一个模型,只是开始。真正的价值在于你能不能在它的基础上做扩展,把复现变成自己的研究工具。
8.1 可以从哪些角度做扩展
最简单的是参数敏感性分析,前面已经说过。稍微深入一点,你可以把单阶段的分布鲁棒模型扩展到多阶段,也就是把日前调度和实时调整结合起来,这会让模型复杂不少,但对能源系统的真实运行更贴近。
更深一层,你可以考虑把这个确定性的多目标模型跟强化学习结合——用分布鲁棒模型批处理生成训练数据,然后用深度强化学习学一个在线调度策略。这个方法我在实际项目里试过,分布鲁棒模型生成的数据分布比较合理,训练出来的策略比直接拿历史数据训练泛化性能好不少。MATLAB的强化学习工具箱现在做小规模实验完全够用。
还一个方向是扩展到电-氢-气-热四网耦合的大系统,把天然气管网也纳入优化范畴。经济性和低碳性都会引入新的维度,模型结构也会发生质的变化。
8.2 代码组织的建议
我的个人习惯是,把整个项目分成数据文件、主模型文件、求解器配置文件和绘图脚本四个部分,中间用函数封装。比如场景生成写成一个函数,目标函数构造写成一个函数,这样后期切换不同场景数S、不同参数组合的时候,只需要改输入参数,不需要改模型主体。
复现论文的时间分配,我建议的是:读懂模型占三成,搭框架写代码占三成,调试和结果分析占四成。别急着一次性把所有功能都加上,先从最简模型跑通,再逐步增加复杂度。每往前走一步,都要回头看对比结果是否合理,确保每一步的基准是稳固的。这样做虽然慢,但是稳。
我在实际做这个模型的过程中,最有成就感的一刻不是代码跑通的那一刻,而是跑通后我尝试着把模糊集的置信度从0.9调到0.99,系统成本只涨了不到3%,但最坏场景下的失负荷率降了20%——那一刻,我才真正理解论文里"分布鲁棒"这个看似高大上的词,到底在解决什么问题。它本质上就是在回答一个非常朴素的问题:你愿意为了应对多大的不确定性,付多少钱。希望这篇博文能帮你把这条路走得更顺一点,也让你在"啪嗒"一声跑出帕累托前沿的那一刻,真正觉得这堆公式和代码不是白调的。