☰
多离散场景分布鲁棒优化电热综合能源系统Matlab实现
2026/10/14 4:41:29 网站建设 项目流程

“数据驱动+多离散场景分布鲁棒+电热综合能源系统优化”这个标题,最近在能源优化方向的讨论里出现频率非常高。组合起来看,它其实不是某一个孤立的算法,而是一整套技术路线:从历史数据出发构造多个离散场景,在场景概率不确定的模糊集上做分布鲁棒优化,同时把电网、热网以及CHP机组、电锅炉这类耦合设备统一进一个调度模型,最后用Matlab把整条链路跑通。这篇文章准备把这个组合从数学原理到代码落地完整拆一遍。我会重点讲清楚为什么要加入“模糊集”、多离散场景的分布鲁棒到底在优化什么、电热综合能源系统的方程怎么搭、以及Matlab代码实现中那些文档里不会写但很关键的地方。适合正在做综合能源优化、分布式鲁棒方向的学生和工程师,也适合想快速复现并跑通一个可参考版本的研究者。

1. 为什么是它:从随机规划到分布鲁棒的演进逻辑

1.1 随机规划与纯鲁棒优化都缺了什么

传统随机规划(Stochastic Programming, SP)的假设是:不确定参数的概率分布已知。实际做电热综合能源调度时,我们手里往往只有历史样本数据,风电出力、光伏出力、热负荷这些变量的真实分布很难精确知道。用有限样本强行拟合出一个经验分布再去做优化,样本不足或数据分布漂移时,求出来的“最优解”会在实际运行中明显走样。

纯鲁棒优化(Robust Optimization, RO)走了另一个极端:它干脆不关心概率,直接把不确定性放进一个集合里,要求任何集合内的实现都能满足约束。这种方案很稳,但代价是过度保守。比如风电出力明明大概率维持在高位,纯鲁棒优化却按照“可能突然掉到零”来预留备用,最终经济性会很差。

分布鲁棒优化(Distributionally Robust Optimization, DRO)正好卡在中间,它假设真实分布落在以经验分布为中心的“模糊集”内,然后在模糊集内寻找最坏情况下的最优期望成本。换句话说,它利用数据信息,又不完全相信数据拟合出来的那一个分布。

1.2 多离散场景分布鲁棒的数学骨架

多离散场景DRO的常见形式可以这样理解:先用历史数据生成K个离散场景,每个场景对应一个不确定性实现,比如某一天的24小时风电出力曲线。记场景s的名义概率为p0_s,通常取1/K,也就是经验分布。

接下来定义模糊集:

P = { p ∈ R^K | ∑ p_s = 1, p_s ≥ 0, ||p − p0||_1 ≤ θ }

这里的θ控制了“真实概率分布与经验分布之间允许偏离多远”。优化问题写成:

min_x max_{p∈P} ∑_{s=1}^K p_s · f_s(x)

其中f_s(x)是场景s下的运行成本。外层的min是决策变量(机组启停、设备出力等),内层的max则是概率分布在最坏情况下取期望成本。

这个形式最大的好处是:它不会像纯鲁棒优化那样只盯着某个极端场景,而是从概率层面做防护。当θ=0时,它退化成随机规划;当θ取到模糊集边界允许的极限时,它又会接近鲁棒优化的保守程度。你可以通过调节θ,在“信任数据”和“防范分布误差”之间连续滑动。

1.3 热点的另一个原因:代码复现难,需求缺口大

这个方向之所以成为“高热点算法”,学术热度是一方面,产业需求是另一方面。综合能源系统强调电、热、气等多种能量的耦合协调,不确定性来源比单一电力系统更多——风电和光伏波动、热负荷变化、用户行为差异,都叠加在一起。数据驱动DRO恰好提供了统一处理的数学框架,论文数量自然快速增长。

但很多论文的公开代码是比较少的,即使公开,也是以Python或GAMS为主,Matlab版本相对分散,完整复现往往要自己填不少坑。这就让“Matlab代码实现”本身就成为一个实实在在的需求,大家不只想看理论推导,更想拿到一个能改参数、能换算例、能和自己的模型对接的代码基础。

2. 电热综合能源系统的模型怎么搭:网络约束与耦合设备

2.1 电力侧与热力侧的物理约束

电热综合能源系统里,电力网络通常用直流潮流近似。节点注入功率满足:

∑_{i∈N} P_g,i − ∑_{j∈N} P_l,j = ∑_{m,n∈B} B_mn · (θ_m − θ_n)

这里的P_g是电源出力,P_l是电负荷,B_mn是支路电纳,θ是相角。实际建模时如果只关心系统级平衡而不做详细网架分析,也可以简化为全系统的电功率平衡约束。

热力侧可以分成两层:热源、热网和热负荷。热网的水力/热力动态建模非常复杂,但目前在调度优化中比较实用的做法是采用稳态热功率平衡模型,即:

∑_{h∈H} Q_source,h = ∑_{d∈D} Q_load,d + Q_loss

更精细一点会引入供水温度和回水温度,热量传输公式为 Q = m·c_p·ΔT,其中m是质量流量,c_p是比热容,ΔT是供回水温差。在Matlab代码里,我一般建议先不做完整热网潮流,而是把热网当成一个带节点温度约束的平衡网络,等基础模型跑通了再逐步加细节。

2.2 耦合设备:CHP、电锅炉与储热的建模细节

电热耦合的核心在设备层。最常见的耦合设备是热电联产机组CHP,它的电出力和热出力不是独立的,而是落在一个可行域内。一个常用的线性化模型是:

P_chp_min ≤ P_chp ≤ P_chp_max Q_chp = R_h2p · P_chp + b_chp

其中R_h2p是热电比,b_chp是常数项。更准确的做法是采用多边形可行域来描述抽汽式CHP的运行区间,但会增加不少约束数量。

电锅炉是另一个重要耦合环节,它把电能转化成热能,模型相对简单:

Q_eb = η_eb · P_eb

η_eb通常取0.95左右。它的作用在于“电转热”,在风电大发时段消纳过剩电力,减少弃风。

储热罐则是时间解耦的关键,约束为:

SOC_t = SOC_{t−1} · (1 − η_loss) + η_ch · Q_ch − Q_dis / η_dis

储热能让热负荷和热源出力在时间上不完全同步,给调度提供额外自由度。这三个设备的建模质量,基本决定了电热协同优化的效果上限。

2.3 不确定性来源与场景集生成

电热综合能源系统的不确定性主要来自风电出力、光伏出力、电负荷和热负荷。生成离散场景时,比较常用的做法是先从历史数据里提取典型日曲线,然后用K-means或者后向场景缩减算法得到K个代表性场景,再叠加预测误差。

举个具体例子:假设某个园区级项目取了最近30天的风电出力和热负荷数据,按小时归一化后,先用K-means聚成20类,再把每个聚类中心加上一个符合历史残差分布的随机扰动,最终生成20个场景。每个场景其实就是一个24维的风电出力向量和一个24维的热负荷向量,存进一个K×T的矩阵里。

这一步有一个容易被忽视的细节:场景缩减时一定要保留尾部场景,也就是那些极端但概率不为零的情况。如果只追求聚类的紧凑性,把风电高发和风电枯竭的场景都抹平了,后续的分布鲁棒防护就失去了意义。

3. Matlab代码实现:场景生成、模糊集构造与求解器配置

3.1 工具链选型:YALMIP+Cplex/Gurobi是主流

Matlab做这类优化问题,最主流的组合是YALMIP建模,配合Cplex或Gurobi求解MILP(混合整数线性规划)。YALMIP的优势在于建模语言接近数学表达式,变量声明、约束拼装、目标函数定义都非常直观,适合快速迭代改模型。

我个人不建议最开始就用纯MATLAB自带求解器intlinprog硬解大规模问题,它面对几百个二进制变量加几千个连续变量的MILP时性能会比较吃力。学术版Gurobi或Cplex能在合理时间内求解更大的算例。

如果一时没有商业求解器,也可以先用开源的Cbc或SCIP顶一下,但要做好求解时间变长的心理准备。

3.2 场景生成与预处理的Matlab实现

场景生成的代码可以这样组织:先读取历史数据矩阵,做归一化,再调用kmeans函数聚类,然后按类内残差生成扰动场景。核心片段如下:

% 历史数据: hist_data 为 n_days x 48 矩阵 (风电24列 + 热负荷24列) K = 20; % 目标场景数 [idx, C] = kmeans(hist_data, K, 'Replicates', 5); scenarios = zeros(K, 48); p0 = ones(K,1) / K; % 名义等概率 for k = 1:K cluster_points = hist_data(idx == k, :); resid = cluster_points - C(k, :); scenarios(k, :) = C(k, :) + resid(randi(size(resid,1)), :); end

这段代码的思路是先用K-means找出K个聚类中心,然后在每个类内部随机抽取一条历史日误差曲线叠加到聚类中心上,形成带随机波动的场景。这种做法的好处是场景不仅保留了典型特征,还保留了真实的波动信息。

注意:kmeans的聚类数K选择不能拍脑袋。可以用轮廓系数和肘部法则先扫一遍,确定合理的K值。K取太小,分布信息不足;K取太大,后续优化问题规模膨胀,MILP求解时间会指数级恶化。

3.3 核心优化模型的代码骨架

下面给出一个简化但结构完整的YALMIP代码骨架。这里把分布鲁棒目标按1-范数模糊集等价形式实现:目标等于场景期望成本加上一个与场景成本极差成正比的鲁棒项。

% 基础参数 T = 24; % 时段数 K = 20; % 场景数 theta1 = 0.1; % 1-范数模糊集半径 ng = 4; % 常规机组数 nchp = 2; % CHP数 nstor = 1; % 储热罐数 % YALMIP变量 % 第一阶段决策:机组启停、常规出力、CHP出力 u_g = binvar(ng, T, 'full'); % 机组启停状态 p_g = sdpvar(ng, T, 'full'); % 常规机组出力 p_chp = sdpvar(nchp, T, 'full'); % CHP电出力 q_chp = sdpvar(nchp, T, 'full'); % CHP热出力 soc = sdpvar(nstor, T, 'full'); % 储热罐状态 % 场景相关决策变量:各场景下的弃风量、电锅炉功率 p_cur = sdpvar(K, T, 'full'); % 弃风功率 p_eb = sdpvar(K, T, 'full'); % 电锅炉功率 Constraints = []; % 电功率平衡,逐场景逐时段 for k = 1:K for t = 1:T Constraints = [Constraints, sum(p_g(:,t)) + sum(p_chp(:,t)) + P_wind(k,t) - p_cur(k,t) ... == P_load(k,t) + p_eb(k,t)]; end end % 热功率平衡 for k = 1:K for t = 1:T Constraints = [Constraints, sum(q_chp(:,t)) + eta_eb * p_eb(k,t) ... == Q_load(k,t) + q_stor_dis(k,t) - q_stor_ch(k,t)]; end end % 储热约束 for t = 2:T Constraints = [Constraints, soc(:,t) == soc(:,t-1)*(1 - eta_loss) ... + eta_ch * q_stor_ch(:,t) - q_stor_dis(:,t)/eta_dis]; end % 目标函数:常规燃料成本 + CHP成本 + 启停成本 + 分布鲁棒惩罚项 cost_exp = 0; for k = 1:K cost_exp = cost_exp + (1/K) * scenario_cost(k, p_g, p_chp, p_cur); end c_robust = sdpvar(1); Constraints = [Constraints, c_robust >= cost_exp + theta1/2 * (max_cost - min_cost)]; obj = cost_exp + theta1/2 * (max_cost - min_cost) + startup_cost; % 求解 options = sdpsettings('solver', 'gurobi', 'verbose', 2, 'mipgap', 0.01); optimize(Constraints, obj, options);

这里的max_cost和min_cost需要用辅助变量线性化:

max_cost = sdpvar(1); min_cost = sdpvar(1); for k = 1:K Constraints = [Constraints, max_cost >= scenario_cost(k)]; Constraints = [Constraints, min_cost <= scenario_cost(k)]; end

这里的核心思路是:在每个场景下都算出一个成本表达式scenario_cost(k),然后用max和min辅助变量把“最坏期望惩罚项”转换成线性约束。这种写法避免了双线性项,也避免了内层max子问题,求解器面对的仍然是一个等价的MILP。

有一点要特别提醒:theta1这个参数不能随便设。它的物理意义是“概率分布允许偏离经验分布的程度”。建议先用小值比如0.05开始试,观察目标成本和调度方案的变化,再逐步增大,找到鲁棒性与经济性的平衡点。

3.4 模糊集线性化:用最简洁形式落地

很多人第一次实现分布鲁棒时,会把概率p_s当成决策变量写进目标函数,结果出现概率变量与成本变量相乘的双线性项,问题规模立刻爆炸。实际上对于1-范数模糊集:

||p − p0||_1 ≤ θ

当p0为等概率时,最坏期望可以显式写成:

max_{p: ||p−p0||_1 ≤ θ} ∑ p_s f_s = (1/K)∑ f_s + (θ/2)·(max_s f_s − min_s f_s)

这个结论成立的前提是θ取值相对较小,概率移动量不会触碰到p_s≥0或p_s≤1的边界。实际工程里θ通常取0.05~0.2,完全落在这个范围内。这样一来,分布鲁棒问题就变成一个“期望成本 + 极差惩罚”的线性目标,求解难度和普通MILP几乎没有差别。

如果还需要限制每个场景概率的独立偏差,可以再叠加∞-范数约束。那种情况下不能再使用这个简洁公式,需要引入一组辅助偏差变量来描述概率模糊集,然后用对偶或列约束生成算法来求解。对于绝大多数园区级综合能源项目,1-范数模糊集已经足够用。

4. 调试实录:常见报错、调参方法与改进方向

4.1 五个典型问题与排查速查表

我在实际跑这类代码时踩过不少坑,整理成一张排查表。

症状可能原因排查与解决思路
求解时间过长场景数K过大,MILP规模爆炸先做场景缩减;设置mipgap为0.01或0.005;检查是否出现双线性项
模型不可行热网负荷无法满足先检查热功率平衡约束,放开储热约束;再用一个足够大的松弛变量定位不可行约束
结果过于保守θ设得过大从θ=0.05开始逐步增加,画成本随θ变化曲线;也可以用验证集数据做交叉选择
风电不用、弃风很多缺电锅炉或储热设备检查电转热和储热容量约束;确认风电场景是否覆盖高位出力时段
YALMIP报solver not found求解器路径未配置或许可证失效用yalmiptest检查求解器状态;重装求解器并添加路径

求解时间过长是出现频率最高的问题。一次典型的20场景、24时段、4台常规机组加2台CHP的MILP,用Gurobi默认参数求解可能需要几分钟到十几分钟。把mipgap放宽到0.01,通常能在几十秒内找到可接受的工程解。这个操作对调度类问题来说完全可行,因为日前的优化结果不需要精确到小数点后四位。

4.2 从0到1的调试顺序(个人习惯)

我自己调试这类模型时会刻意分四步走,而不是一口气把完整模型扔给求解器。

第一步,跑确定性模型。先不考虑任何场景,用预测值直接求解,确保电功率平衡、热功率平衡、储热罐运行时间常数这些基本约束都在合理区间。第二步,把所有场景同时加入但保持θ=0,也就是退化成随机规划。这一步能检验场景矩阵构造的正确性,比如场景矩阵里有没有NaN、维度有没有对不上、每个时段的风电容量是不是超过了装机上限。第三步,加入分布鲁棒惩罚项,把θ从0调到0.05,看结果是否平滑变化。最后一步,才去调整模糊集半径、备用比例、储热容量等参数做敏感性分析。

如果第二步就出现不可行,问题几乎都出在场景数据本身。比如某几个场景下风电出力很低,而常规机组和CHP的爬坡能力又限制得太死,导致部分时段无法满足电负荷。这时要么增加机组出力上限,要么引入可中断负荷,而不是盲目怀疑分布鲁棒部分。

4.3 从复现到改进:几个值得深挖的方向

代码复现只是起点,后续有几个方向我认为价值很高。

第一个改进方向是更换模糊集类型。1-范数模糊集实现简单,但理论上Wasserstein模糊集的统计收敛性质更好,因为它在度量空间上定义了真实的概率距离。切换到Wasserstein球后,目标函数会变成一个带对偶变量的线性规划,实现难度会增加一个层级,但鲁棒性描述更精细。

第二个方向是做多阶段扩展。目前的模型是两阶段,第一阶段做日前决策,第二阶段看场景做调整。实际运行中还需要日内滚动修正,可以引入模型预测控制或者在每时段重新求解滚动优化问题,对应Matlab代码就是外层加一个for t = 1:T循环,每次只提交未来一个窗口的决策。

第三个方向是加入碳约束或碳交易成本。电热综合能源的减排潜力很大,CHP机组、电锅炉、储热的协同方式会直接影响碳排放量。把碳价乘到机组出力项里作为一个成本系数,不需要改动模型结构,就可以考察不同碳价下调度方案的变化趋势,对工程方案论证很有帮助。

最后还有一个实操性的建议:所有参数最好集中放在独立的参数文件里,不要散落在脚本各处。我见过很多代码把θ、K、T都写在循环里面,改一次参数要翻好几个文件,极易出错。用结构体比如params.K、params.theta1统一管理,后期做敏感性分析时效率会高很多。这看起来是小习惯,但在你连续跑十组不同参数对比时能节省大量时间。

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

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

立即咨询