☰
Matlab实现电热综合能源系统多场景分布鲁棒优化调度方法
2026/10/11 3:31:51 网站建设 项目流程

先聊一个真实调度场景:早上8点预测下午3点风大,你按这个预测排好了机组出力,结果到了2点半风突然变小,电锅炉还在满功率吃电,储热罐可用热量又快见底,这时候再调整机组爬坡已经来不及了。做电热综合能源调度的人,多少都经历过这种“预测一时爽,调度两行泪”的时刻。

纯靠点估计做决策,迟早被不确定性教做人;但完全按最坏场景规划,成本高得离谱,电厂和热网公司都接受不了。于是这些年“数据驱动 + 分布鲁棒优化”这套思路越来越热,核心就是:利用历史数据构造场景,再在场景周围留一个“模糊集”来兜底,最终在“经济性”和“鲁棒性”之间取一个可调的平衡点。

这篇文章围绕多离散场景分布鲁棒(Distributionally Robust Optimization, DRO)方法,聊聊怎么在电热综合能源系统里落地一套完整的Matlab调度程序。我会从问题建模、不确定性处理、模糊集构造、对偶转化、代码框架到踩坑实录全部过一遍,适合正在做综合能源调度、园区能源管理相关课题的研究生,以及想从确定性优化转向鲁棒优化的工程师参考。

1. 先把问题说透:电热综合能源系统为什么难优化

1.1 电和热两种能量耦合,不是两套独立系统

电热综合能源系统之所以让人头疼,是因为电、热本身就是强耦合的。典型园区系统里主要有这么几类设备:

  • 热电联产机组(CHP):这是电-热耦合的核心。抽汽式CHP的发电量和供热量必须在可行域内联动,发多了电往往热也会多,灵活性受限。
  • 电锅炉(EB):把电转化成热,相当于给系统增加了一条“电转热”的灵活路径,但它本质上是负荷,会增加电网购电压力。
  • 储热罐(TES):储热罐是解耦电-热“强绑定”的关键缓冲装置,热负荷高峰时放热,热负荷低谷时充热,相当于给系统提供一个时间维度上的平移能力。
  • 常规火电/上级电网购电:提供电功率平衡的兜底手段。
  • 风电/光伏:这里是不确定性的主要来源,也直接决定了为什么要做分布鲁棒。

电网侧需要满足:CHP发电 + 风电实际出力 + 购电 = 电负荷 + 电锅炉耗电。

热网侧需要满足:CHP供热量 + 电锅炉供热量 + 储热罐放热 = 热负荷 + 储热罐充热 + 热网损失。

问题的难点不在方程多,而在于CHP 的发电和供热捆绑在一起,风电又随机波动。一旦风电预测不准,系统只能通过快速调整CHP或者改变电锅炉出力来平衡,但这两者又牵动着热力平衡,牵一发动全身。

1.2 三种不确定性处理思路,为什么我选分布鲁棒

对风电出力和负荷预测误差的处理,现在主流有三条路线,它们之间存在本质区别:

方法不确定集合优化目标对数据的依赖保守程度
确定性优化无,直接使用预测值单场景经济最优只要预测值最低,但容易失衡
随机规划(SAA)若干离散场景及概率场景期望成本最小需要大量场景中低,依赖场景质量
传统鲁棒优化盒式/椭球不确定域最坏情况下成本最小只需不确定变量的上下界高,过于保守
分布鲁棒优化(DRO)以历史分布为中心的模糊集最坏概率分布下的期望成本最小需要历史数据构造场景和模糊集可调的中间水平

随机规划的问题是:你给的场景概率分布是固定的,但实际风电误差分布可能跟假设偏差很大;传统鲁棒的问题是:它只关心最坏情况,凡是落在盒式集合内的可能值都按最坏结果兜底,最终调度成本可能比实际最优贵20%以上。

分布鲁棒走的是中间路线——只假设真实分布落在经验分布周围的一个“模糊集”内,然后优化最坏分布下的期望成本。当历史数据足够多时,模糊集半径可以收得很小,结果趋近随机规划;当数据不充分或者你对分布没信心时,可以调大半径,结果向传统鲁棒靠拢。这个“可调”特性在工程里非常实用,因为它让你能用一套算法框架去适配不同数据质量的项目。

1.3 多离散场景这个“多”字,到底指什么

标题里的“多离散场景”可以拆成两个层面理解。

第一层是用有限离散场景近似经验分布:把历史风速、历史负荷误差数据通过聚类或者采样,归纳成几十个代表性场景,每个场景对应一组24小时的风电出力曲线。这样随机变量就从“连续分布”退化为“带概率权重的离散点集”,计算规模可控。

第二层是模糊集也按离散场景构造:分布鲁棒优化不需要显式描述连续分布,而是以“这些离散场景组成的经验分布”为中心,向外扩一个半径为alpha的距离球(通常是Wasserstein距离)。只要真实分布没有超出这个球,最坏情况下的期望成本就在模型掌控范围内。

这种“离散场景 + 模糊球”的组合,既保留了随机规划对场景信息的利用,又继承了鲁棒优化对不确定性的兜底能力,是这个方向近几年成为热点的关键。

2. 分布鲁棒优化的建模与转化:怎么把min-max问题变成可求解问题

2.1 先建立确定性调度主模型

整套优化模型可以表达为:

目标:最小化系统总运行成本,包括CHP燃料成本、向上级电网购电成本、弃风惩罚、切负荷惩罚。

约束:电功率平衡、热功率平衡、CHP可行域约束、电锅炉出力上下限、储热罐SOC连续性约束与容量约束、购电上下限、爬坡约束等。

其中CHP运行可行域是关键约束,通常用一组线性不等式围成的多边形描述,即满足:

  • P_chp_min <= P_chp_t <= P_chp_max
  • H_chp_min <= H_chp_t <= H_chp_max
  • P_chp_t + c1 * H_chp_t <= c2
  • c3 * H_chp_t - P_chp_t <= c4

这四个不等式基本可以刻画抽汽式CHP的可行域。如果你的CHP是背压式,可以直接简化为P = c * H,那电热耦合更紧,调度难度更大。

储热罐模型相对简单,但要注意不能同时充放热的约束:

  • S_t = S_{t-1} + eta_c * H_char_t - H_dis_t / eta_d - eta_loss * S_{t-1}
  • 0 <= H_char_t <= H_char_max * u_c_t
  • 0 <= H_dis_t <= H_dis_max * u_d_t
  • u_c_t + u_d_t <= 1

这里的u_c_t和u_d_t是0-1变量,一旦加进去,模型就变成了MILP(混合整数线性规划)。如果系统规模不大,直接用YALMIP+ Gurobi求解没问题;如果规模大,要考虑用启发式或者滚动时域控制去压规模。

2.2 把不确定性装进去:从SAA到DRO

引入风电不确定性后,简化的目标函数形式变成一个min-max双层结构:

外层min是调度决策变量x(机组出力、储热罐充放、购电等),内层max是针对模糊集D中所有可能概率分布P,最大化期望成本。

DRO目标含义
min_x max_{P in D} E_P[f(x, xi)]在“最坏的合理分布”下,期望成本最小

这里xi就是随机变量(比如风电误差向量、负荷误差向量),D就是模糊集。

模糊集最常见的构造方式是Wasserstein球:

D = { P | W(P, P_hat) <= alpha }

其中P_hat是历史数据构造的经验分布,alpha是模糊集半径。Wasserstein距离衡量两个分布之间的“搬运成本”,直观理解就是:要把经验分布P_hat变成真实分布P,最少需要“搬运”多少概率质量。alpha越大,真实分布离经验分布可以越远,模型越保守。

工程实现中,我用的是1-范数Wasserstein距离,因为对偶转化后能保持线性约束结构,Gurobi和CPLEX可以直接吃下。如果上2-范数,模型会带二次约束,求解器压力明显变大,除非你确有必要,我不太推荐。

2.3 对偶转化的三板斧

直接求解min-max问题是不现实的,实际做法是把它对偶成一个单层的min问题。以Wasserstein模糊集为例,核心思路分三步:

第一步,内层max对偶变换。在凸目标函数和凸模糊集条件下,内层最大化问题可以等价地转化为一个关于对偶变量lambda的最小化问题。这一步让“最坏分布”消失,换来的是对每个离散场景引入一组额外约束和辅助变量s_i。

第二步,引入场景级最优值函数。每个离散场景xi_i都会产生一个“最坏成本”的支撑函数,通过引入辅助变量s_i,把对每个场景的max项线性化。

第三步,最终得到形如:

  • min_{x, lambda>=0, s_i} lambda * alpha + (1/N) * sum(s_i)
  • 约束:lambda >= 0
  • 对每个场景i:s_i >= f(x, xi_i) - lambda * d(xi_i, xi_j) 的某种线性化形式

这里d是场景之间的Wasserstein距离项,f是当前决策x在场景xi_i下的运行成本。

这个公式写出来可能有点抽象,但在代码里的操作其实很简单:不要自己手推全部对偶约束,而是用小规模测试(3个场景、3个时段)去验证对偶转换后的目标值和暴力枚举max结果一致。我一开始直接套大模型,结果怎么都不对,最后缩小规模一步步检查才定位到是某个极端场景下的约束没写全。

3. Matlab代码实现:从零搭一套分布鲁棒调度程序

3.1 代码框架与文件规划

整个项目我分成五个文件维护,结构清晰,也方便复现:

项目目录/ ├── main.m % 主程序入口 ├── set_parameters.m % 设备参数定义 ├── generate_scenarios.m % 历史数据读取 + 场景生成/聚类 ├── build_dro_model.m % YALMIP建模仿真 ├── plot_results.m % 结果可视化

main.m 的结构就是典型的“参数-场景-建模-求解-画图”五段式:

%% main.m clc; clear; close all; % 1. 参数设置 param = set_parameters(); % 2. 读历史风速数据,生成离散场景 [scen, prob] = generate_scenarios(param); % 3. 构建DRO模型并求解 [result, model] = build_dro_model(param, scen, prob); % 4. 画图 plot_results(result, param);

这里有一点值得强调:场景生成和建模是解耦的。如果你后续想换数据、换聚类方法,只需要动generate_scenarios这一个文件,不需要碰主模型,这个设计能给你后续调参省很多事。

3.2 场景生成:K-means聚类 + 拉丁超立方采样

场景生成模块的核心目标是:把大量历史风电出力数据压缩成几十个代表性离散场景。我用的是两步法。

第一步,拉丁超立方采样(LHS)生成海量预测误差样本。为什么要用LHS而不是直接蒙特卡洛抽样?因为LHS能保证样本点在概率空间内覆盖更均匀,同样的样本量下,LHS构造的经验分布更稳定,模糊集半径alpha可以选得更小。

第二步,用K-means对样本聚类,取聚类中心作为代表场景,按每个簇的样本比例分配概率。聚类数N我一般取20~50之间。少于20个场景,分布信息损失太严重,模糊集半径会被迫加大,成本偏向保守;多于50个场景,模型规模膨胀明显,MILP求解时间会从几分钟涨到一个小时。

核心代码示意:

% generate_scenarios.m 片段 % 历史误差样本: err_hist (N_hist x T) err_hist = load_historical_data(); % LHS生成候选样本 candidate = lhsdesign(N_samp, T); % 将[0,1]区间的LHS样本映射到经验误差分布 err_samp = quantile(err_hist, candidate); % K-means聚类 [idx, C] = kmeans(err_samp, N_scen, 'Replicates', 10); prob = histcounts(idx, N_scen) / N_samp; % C就是N_scen x T的离散场景矩阵 scen = C;

一个容易忽略的坑:聚类前要对数据进行归一化,尤其是风电和负荷量纲不同的时候。有的风电场装机容量500MW,负荷可能才50MW,如果不归一化,聚类结果会完全被风电数据主导,负荷误差场景根本体现不出来。我通常对每个随机变量单独归一化到[0,1]区间,构造模糊集后再反归一化回去。

3.3 核心模型:YALMIP写DRO的关键代码

模型部分用YALMIP建模,求解器接口用Gurobi或CPLEX。整个DRO模型在YALMIP里非常直白,因为分布式鲁棒转化后的模型本质上是一个带额外辅助变量的MILP。

决策变量定义片段:

% build_dro_model.m 片段 T = param.T; P_chp = sdpvar(T, 1); % CHP电出力 H_chp = sdpvar(T, 1); % CHP热出力 P_eb = sdpvar(T, 1); % 电锅炉耗电 H_eb = sdpvar(T, 1); % 电锅炉供热 P_buy = sdpvar(T, 1); % 购电 SOC = sdpvar(T+1, 1); % 储热罐状态 H_char = sdpvar(T, 1); % 充热 H_dis = sdpvar(T, 1); % 放热 % 辅助变量:DRO对偶变量和场景上界 lambda = sdpvar(1); s_var = sdpvar(param.N_scen, 1);

目标函数的YALMIP写法:

% 确定性成本部分 obj_det = sum(param.c_fuel .* P_chp) + sum(param.c_buy .* P_buy) ... + sum(param.c_wind * (scen_forecast - P_wind_used)); % DRO最坏分布期望附加项 obj_dro = lambda * param.alpha + (1/param.N_scen) * sum(s_var); objective = obj_det + obj_dro;

核心约束张力在场景环节。对每个离散场景i,s_var(i)要大于等于“当前决策在场景i下的成本 - lambda乘以场景距离调整项”。这个调整项是用来约束真实分布不能离经验分布太远的“软约束”。

constr = []; constr = [constr, lambda >= 0]; for i = 1:param.N_scen % 提取场景i的风电出力 wind_i = scen(i, :)'; % 该场景下的最坏成本上界(简化示意) cost_i = sum(param.c_wind * (wind_i - P_wind_used)) ... % 弃风惩罚项 + sum(param.c_pen * (P_load - P_supply_i)); % 切负荷惩罚项 % Wasserstein距离项简化为场景差分的1-范数(按需调整) dist_i = sum(abs(scen(i,:) - scen_mean), 2) / T; constr = [constr, s_var(i) >= cost_i - lambda * dist_i]; end

注意这只是一个示意片段,不同项目的目标函数、惩罚系数、场景维度差异很大,代码要根据自己系统的约束修改。

模型组装好后直接调求解器:

ops = sdpsettings('solver', 'gurobi', 'verbose', 2); sol = optimize(constr, objective, ops); % 检查求解状态 if sol.problem == 0 disp('求解成功'); else disp(sol.info); end

3.4 结果后处理与对比实验

计算结果别只看最优成本一个数。我做这类项目时,一般会画三张图:

第一张:CHP、电锅炉、购电、风电的24小时电功率平衡堆叠图。重点看有没有出现“CHP贴着下限运行还不得不弃风”的时段,如果存在,说明模糊集半径或储热罐调度参数可能需要调整。

第二张:热功率平衡图,叠加储热罐SOC曲线。这张图能直观看出储热罐有没有起到“削峰填谷”作用。如果SOC曲线全程贴着上限或下限跑,说明储热罐容量没被合理利用,可以考虑调整充放热的价格参数。

第三张:成本对比条形图。分别跑确定性模型、SAA随机规划模型、DRO模型,画出总成本,再在DRO模型里取几个不同的alpha值,看成本是怎么随着alpha增大而升高的。这张图是论文或汇报里最有说服力的结果。

我在实际项目中测下来,alpha从0.05增大到0.3,成本大约会上升5%-15%,但系统的“实际失负荷小时数”会大幅下降。这个trade-off曲线建议你在汇报时重点展示,比堆一堆公式更能让导师或者甲方理解分布鲁棒的工程价值。

4. 调试排坑:我在这个项目上踩过的五个坑

4.1 对偶变量维度不匹配导致YALMIP报错

最常见的问题:s_var定义成标量,但场景循环里却按向量索引使用。YALMIP对维度非常敏感,一旦s_var(i)没法索引,直接报“Subscripted assignment dimension mismatch”。

解决技巧:在定义变量后用assert语句检查维度:

assert(length(s_var) == param.N_scen, 's_var维度与场景数不匹配');

这个检查放在建模前,能让你第一时间定位是定义问题还是约束问题。

4.2 模糊集半径alpha不是我拍脑袋定的

alpha太小,模型形同虚设,基本等价SAA;alpha太大,成本膨胀严重,失去意义。理论上有经验公式alpha和样本量N的关系是alpha = O(1/sqrt(N)),但实际操作中我推荐交叉验证法。

把历史数据切分成训练集和验证集。用训练集构造经验分布和模糊集,求解调度决策,再拿验证集里没参与建模的“真实场景”去回测,看成本分布情况。选能覆盖90%验证场景不切负荷的最小alpha。这个方法虽然要多花一点时间,但胜在可解释性很强,汇报时也容易被接受。

4.3 热功率平衡约束导致无解

电锅炉和储热罐同时参与热平衡时,很容易出现“热量来源太多”导致热功率过剩,约束冲突无解。我排查过多次,原因基本都出在储热罐的充放热0-1约束没写完整,充热和放热变量同时为正,热平衡被双倍计入。

一个排查技巧:求解无解时,先把储热罐的0-1约束和SOC约束单独拿出来,固定所有电出力变量,只求解热子系统。如果热子系统仍有解,再逐步加回电侧约束,用二分法锁定冲突约束。

4.4 场景数一多,求解时间爆炸

50个场景、24个时段,决策变量里再带上0-1储热变量,Gurobi解一个MILP可能要20分钟。后来我的处理方式是:

  • 先用大场景数做预分析,确定合适的alpha范围;
  • 正式求解时把场景聚类数量压到30个左右;
  • 同时给MILP设置一个相对最优间隙(mipgap),比如5%,很多情况下Gurobi能在5分钟解决战斗。

实际工程决策场景下,5%的间隙完全可接受,花15分钟追求0.1%的经济提升其实意义不大。

4.5 数据中心化处理不当导致模糊集失效

这个坑比较隐晦。经验分布P_hat在DRO中的位置非常关键,一旦场景数据没有按变量均值中心化,Wasserstein距离计算出的alpha实际含义会偏离预期。解决方法是构造模糊集前的所有场景均做零均值、单位方差标准化,等对偶转化完成、得到决策结果后,再把结果反标准化回实际物理量纲。

我在这里吃过一次亏,花了一周时间比对结果,最后发现是归一化以后忘了在距离项里乘回尺度系数,导致alpha的实际几何意义完全不对。

5. 后续扩展:这套框架还可以往哪走

数据驱动分布鲁棒这套框架的价值,不局限于电热综合能源系统。我做完这个项目后发现,同样的“离散场景 + Wasserstein模糊集 + 对偶转化”三段式,可以直接平移到很多相关问题上:

  • 含氢储能的综合能源系统调度,氢气储能的不确定性和储热罐很像,但时间尺度更长;
  • 电动汽车聚合商参与电力市场的投标策略,充电行为的不确定性正好用场景描述;
  • 园区级微电网与配电网的互动优化,分布式光伏出力波动比风电更剧烈,分布鲁棒的优势更能体现。

如果想把项目做成真正的论文级别,还可以加一套两阶段分布鲁棒模型:第一阶段决定机组开停机和储热罐充放计划,第二阶段在不确定性实现后做出力调整。两阶段的DRO模型更贴近真实调度流程,但求解复杂度会显著上升,需要引入Benders分解或者割平面方法,这是另一个值得写一篇文章的话题了。

我自己实际操作下来的体会是:分布鲁棒优化最难的部分不是数学推导,而是对不确定性数据的认知——你对数据越了解,模糊集半径就选得越准,优化结果也就越有说服力。如果一上来就追求复杂的模糊集和花哨的求解器,反而容易忽略问题的本质。先把确定性模型吃透,再把场景注入,最后加模糊集兜底,一步一个脚印,这个方向其实没有想象中那么高不可攀。

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

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

立即咨询