先交代一个背景:我接触综合能源系统优化这个方向大概是从一个园区级电-气-热联供项目开始的,当时最大的困扰不是模型有多复杂,而是风光出力的随机波动让确定性优化算出来的“最优方案”一到实际运行就完全走样。后来把新能源出力不确定性显式建模进去,用Matlab搭了一套协同优化框架,才真正解决了调度方案可落地的问题。这篇就把整个思路、建模细节、代码实现和踩坑记录一次性梳理清楚,给正在做类似课题的同行一个可以直接参考的模板。
这套东西解决什么问题?简单说,在一个同时包含电、气、热三种能源形式,并且接入了光伏和风电的系统里,如何在风光出力不确定的前提下,用Matlab求解出未来24小时各台设备(燃气轮机、电锅炉、储能、购电等)的最优出力计划,让总运行成本最小,同时满足所有负荷需求和设备运行约束。适合正在做综合能源系统优化调度、新能源消纳、多能互补课题的学生和工程师参考,也适合刚接触Yalmip和求解器做优化计算的朋友拿来练手。
1. 综合能源系统协同优化,到底在优化什么
在做代码之前,一定先把“优化什么”这个问题想透。综合能源系统协同优化,表面上是求一个目标函数的最小值,实际上是在处理多能源耦合、时间耦合、不确定性耦合三个层面的问题。
1.1 系统组成与能量流梳理
以我常用的园区级综合能源系统为例,包含的基本设备如下:
- 供电侧:上级电网购电、光伏(PV)、风电(WT)、燃气轮机(GT)
- 供热侧:燃气轮机余热回收、燃气锅炉(GB)、电锅炉(EB)
- 储能环节:电储能(ESS)、蓄热罐(TSS)
- 负荷侧:电负荷、热负荷、气负荷
这里最核心的耦合关系是燃气轮机——它同时消耗天然气、产生电能和热能,这就是所谓“热电联产”的耦合效应。电锅炉则是把电能转化为热能,实现了电-热两种能源的转换,属于另一种耦合方式。
协同优化的“协同”二字,就体现在这些设备之间的出力分配不是孤立的。比如夜间风电大发时,电负荷较低,如果只盯着电力系统,可能会选择弃风;但如果把热负荷也纳入优化范围,就可以让电锅炉在夜间多消耗风电来供热,从而既减少了弃风,又降低了燃气锅炉的用气成本。这种跨能源形式的互补效益,是单一能源系统优化完全无法体现的。
1.2 时间耦合:为什么必须做日前调度
综合能源系统优化通常是日前调度,时间尺度是24小时,步长1小时。这意味着优化变量不只是某一时刻的设备出力,而是24个时刻的完整序列。
时间耦合主要来自储能设备。储能电池的SOC(荷电状态)在t时刻的状态,取决于t-1时刻的状态和t时刻的充放电功率:
SOC(t) = SOC(t-1) + [P_ch(t)·η_ch - P_dis(t)/η_dis]·Δt / E_cap
这个约束把24小时的决策变量串成了一条链。如果只做单时刻优化,储能就失去了“削峰填谷”的意义。燃气轮机的爬坡约束也属于时间耦合——上一小时出力100MW,下一小时跳到150MW,往往受限于爬坡速率,这就需要前后时刻的决策变量联动。
1.3 不确定性:为什么新能源出力不能当确定值处理
风电和光伏出力的随机性,是综合能源系统优化与普通电力调度最大的区别。实测数据显示,光伏出力受云层遮挡影响,5分钟内波动可达额定容量的20%以上;风电的日前预测误差通常在15%到25%之间。
如果不考虑这种不确定性,把预测曲线当作真实出力带入优化模型,得到的调度方案会存在两个问题:
- 当实际出力低于预测值时,系统可能出现功率缺额,需要临时高价购电或切负荷
- 当实际出力高于预测值时,可能出现弃风弃光,造成浪费
所以,“计及新能源出力不确定性”这个前置条件,本质上决定了优化模型的结构——是确定性优化(不考虑)、随机优化(考虑概率分布)、鲁棒优化(考虑最坏情况)还是分布式鲁棒优化(考虑分布模糊)——这会直接影响约束条件和求解算法的选择。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 不确定性建模:三种主流方案与选型逻辑
处理新能源出力不确定性,Matlab环境下常见的做法有三种:场景法、鲁棒优化、区间优化。每种我都实际跑过,说下适用场景和坑点。
2.1 场景法:最直观,也最容易上手
场景法的思路是:认为新能源出力服从某个概率分布,通过蒙特卡洛抽样生成大量可能的出力场景,再用场景削减技术保留少数有代表性的场景,最终把随机优化问题转化为确定性的多场景优化问题。
我实现时通常这么写:
- 用历史出力数据拟合风电/光伏出力的概率分布(常用Beta分布描述光伏,Weibull分布描述风速再转换成功率)
- 抽样生成N=1000个场景
- 用同步回代削减法(K-means聚类也可以)把场景数削减到10~20个
- 将各场景作为确定性场景,目标函数改为各场景成本的加权期望
这个方案最贴近物理意义,代码实现也不复杂,多场景并行求解时Matlab配合Yalmip建模非常顺手。
2.2 鲁棒优化:追求“最坏情况下的最优”
鲁棒优化的核心是不需要知道概率分布,只需要给不确定性定义一个集合(通常是盒式集合或椭球集合),优化目标是使最坏情况下系统的运行成本最小。
我当时实测的体会是,鲁棒优化对数据的要求最低,只要有预测出力的上下界就能建模,但结果往往偏保守。尤其是在风光渗透率较高的场景下,鲁棒解的运行成本可能比确定性方案高出20%甚至更多,这在工程上不太容易让决策者接受。
如果要用鲁棒优化,建议把不确定集合参数化,比如盒式集合引入Γ系数控制保守程度:
|U_wt| ≤ Γ_wt, |U_pv| ≤ Γ_pv
当Γ取0时退化为确定性模型,取最大值时完全保守,通过调节Γ可以在这两者之间找平衡。
2.3 区间优化与模糊集:折中的选项
区间优化只需要预测出力的上下界,得到一个区间形式的解,语义是“真实出力落在这个区间内,系统就能安全运行”。它比鲁棒优化更灵活,但求解出来的结果还要经过区间可行性校验,处理起来稍繁琐。
另外近年的热点是分布鲁棒优化——假设出力分布落在以经验分布为中心的一个模糊集内,优化最坏分布下的期望成本。它对数据量的要求比场景法低,又不像鲁棒优化那么保守,但求解难度明显上升,一般要借助力矩模糊集和对偶变换处理。
2.4 我的方案选择:场景法为主
在本项目里我最终选择的是场景法,理由很实际:
- 项目的目标是给出可解释、可复现的调度方案,场景法的结果可以逐个场景分析,便于跟非专业背景的决策者沟通
- 求解规模适中,10个场景、96个时段的混合整数线性规划(MILP)问题用Cplex求解一般几十秒内能收敛,迭代调试体验好
- 后续如果要扩展为两阶段随机规划,场景法可以直接套用,代码改动量最小
如果你的研究目标偏理论创新,鲁棒或分布鲁棒更有文章可做;但如果你要解决的是“实际运行方案怎么定”,我真的建议先跑通场景法,再考虑拓展。
3. 优化模型构建:目标函数与约束条件逐条拆解
模型是整个协同优化的核心。我建议用数学语言把问题写清楚再动代码,否则Matlab里写出来的约束会非常混乱,出错后极难排查。
3.1 目标函数:多类成本叠加
综合考虑运行经济性与可靠性,目标函数定义为一天内的总运行成本最小,包括购电成本、燃气成本、设备运维成本,以及失负荷惩罚成本:
min F = Σ_t [ C_elec(t) + C_gas(t) + C_om(t) ] + C_penalty
- 购电成本 C_elec(t) = price_buy(t) × P_grid(t),分时电价的峰谷时段影响很大
- 燃气成本 C_gas(t) = price_gas × (V_gt(t) + V_gb(t)),燃气轮机与燃气锅炉用气量按效率折算
- 设备运维成本 C_om(t) = Σ_i k_i × P_i(t),运维系数按设备类型取值,光伏和风电一般取得很低
- 失负荷惩罚 C_penalty = λ_pen × (L_elec_curt + L_heat_curt),惩罚系数一般设置远高于正常购电成本,确保优化算法优先避免切负荷
在随机优化的框架下,目标函数变成:
min F = Σ_s π_s × F_s
其中π_s是各场景的概率权重,F_s是场景s下的运行成本。这样就把“不确定性”量化进了目标函数——某场景发生的概率越大,对优化结果的影响就越大。
3.2 功率平衡约束:不可违反的底线
功率平衡约束是任何调度模型的硬约束。综合能源系统的特别之处在于,存在独立的电力平衡和热力平衡,且二者通过热电联产设备联动。
电力平衡(每个时段t,每个场景s):
P_grid(t) + P_pv(t) + P_wt(t) + P_gt(t) + P_ess_dis(t) = L_elec(t) + P_eb(t) + P_ess_ch(t) + P_grid_export(t)
热力平衡:
H_gt(t) + H_gb(t) + H_eb(t) + H_tss_dis(t) = L_heat(t) + H_tss_ch(t)
注意,等式两边的设备已包括储能,储能充电即相当于负荷,放电即相当于电源。
3.3 设备运行约束:决定模型复杂度的关键
设备约束包括出力上下限、爬坡约束、启停时间约束、储能SOC约束,每类都需要细看。
以燃气轮机为例,它的电-热耦合关系是模型的重头戏。工程上常用可行运行区域来描述热电联产机组的出力范围,简单处理时采用定热电比模型:
H_gt(t) = η_chp × P_gt(t)(定热电比简化)
严格一点应该用多边形可行域,用一组线性不等式刻画电出力与热出力之间的约束关系。后者更接近实际机组特性,但约束数量明显增加,求解难度也随之上升。
储能约束是另一大重点,因为它在24小时上耦合:
SOC(t) = SOC(t-1) + [P_ch(t)·η_ch - P_dis(t)/η_dis]·Δt / E_cap
SOC_min ≤ SOC(t) ≤ SOC_max
SOC(0) = SOC(24)(循环约束,确保储能的日平衡)
0 ≤ P_ch(t) ≤ P_ch_max·u_ch(t)
0 ≤ P_dis(t) ≤ P_dis_max·u_dis(t)
u_ch(t) + u_dis(t) ≤ 1
这个 u_ch 和 u_dis 是0-1变量,用来防止储能同时充放电。它们的存在让模型变成了混合整数线性规划,这也是为什么求解时间会随场景数和时段数明显上升。
3.4 不确定性相关的场景约束
场景法下,每个场景都要满足各自的平衡约束和设备约束,相当于把24小时的确定性优化问题复制了Ns份,再通过目标函数的概率加权把“期望成本”统一起来。
这里有一个细节值得注意:决策变量分为第一阶段(here-and-now)和第二阶段(wait-and-see)。日前调度中,与电网的购电协议、机组启停计划通常是日前就要确定的,属于第一阶段决策,在所有场景下保持一致;而储能出力和部分设备出力可以在日内根据实际风光出力调整,属于第二阶段决策,允许随场景变化。
这种划分让模型更贴近实际,但代码实现时要注意区分变量是带场景索引还是不带。我在初版代码里把购电功率也做成了随场景变化,结果算出来的解虽然在数学上最优,但实际调度中根本做不到,因为没有哪个售电公司允许你第二天根据实际出风再决定买多少电。后来把购电改为第一阶段变量,模型才真正可落地。
4. Matlab实现:代码框架与核心模块精讲
模型搭好后,Matlab实现其实就分三步:定义参数、构建优化模型、调用求解器。我用的是Yalmip工具箱作为建模层,求解器选Cplex,这套组合在学术界和工程界都非常成熟,文档丰富,遇到问题容易搜到答案。
4.1 代码整体框架设计
我习惯把工程拆成几个独立的脚本和函数,用注释区隔,方便单模块测试:
matlab复制%% 主程序 main.m
% 1. 数据准备:读取负荷曲线、风光出力场景、设备参数
% 2. 建立决策变量
% 3. 写入目标函数与约束条件
% 4. 调用求解器求解
% 5. 结果可视化与报表输出
实际操作中我推荐用结构体统一管理参数,避免脚本里散落大量魔法数字。比如:
matlab复制%% 初始化系统参数
para = struct();
para.T = 24; % 调度时段数
para.Ns = 10; % 场景数
para.dt = 1; % 时间步长,单位h
% 设备容量
para.GT.Pmax = 60; % 燃气轮机最大电出力,MW
para.GT.Pmin = 15; % 燃气轮机最小电出力,MW
para.GT.eta_e = 0.35; % 发电效率
para.GT.eta_h = 0.45; % 热回收效率
para.GT.ramp = 20; % 爬坡约束,MW/h
para.ESS.Ecap = 50; % 储能容量,MWh
para.ESS.Pmax = 15; % 储能最大充放电功率,MW
para.ESS.eta_ch = 0.95; % 充电效率
para.ESS.eta_dis = 0.95; % 放电效率
para.ESS.SOC_min = 0.2;
para.ESS.SOC_max = 0.9;
这样写的好处是:改设备参数时只需改结构体,不需要动模型核心代码;换一套数据时,只需替换数据读取部分,主逻辑完全复用。
4.2 场景生成与削减:用Matlab实现蒙特卡洛抽样
场景生成我按两步走,先抽样再削减。抽样用Matlab自带的随机数生成函数,配合概率分布对象:
matlab复制%% 场景生成代码示例
% 光伏出力:Beta分布拟合
rng(2024);
% 从历史数据拟合Beta分布参数(示意)
alpha_pv = 2.5; beta_pv = 3.0;
pv_scenarios = betarnd(alpha_pv, beta_v, para.N_scene, para.T) .* para.PV.Pmax;
% 风电出力:先抽风速(Weibull分布),再通过功率曲线转换
% Weibull分布参数 k=2.2, c=8.5(按当地测风数据)
wind_speed_scen = wblrnd(2.2, 8.5, para.N_scene, para.T);
% 典型风机功率曲线近似
v_ci = 3; v_co = 25; v_r = 12;
P_wt = zeros(size(wind_speed_scen));
idx = wind_speed_scen >= v_ci & wind_speed_scen < v_r;
P_wt(idx) = para.WT.Pmax .* (wind_speed_scen(idx) - v_ci) ./ (v_r - v_ci);
P_wt(wind_speed_scen >= v_r & wind_speed_scen <= v_co) = para.WT.Pmax;
场景削减我推荐用同步回代削减法,核心思想是:把概率距离最近的场景不断合并,直到剩下预设的场景数。Matlab实现大概50行左右,核心循环是计算场景间的距离矩阵、找出最小距离的场景对、合并并累加概率。K-means聚类的效果也不错,但削减后的场景可能不保留原始场景的物理形态,同步回代削削减出来的场景都是抽样得到的真实轨迹,直观上更可信。
4.3 Yalmip建模:从数学公式到代码的无缝迁移
用Yalmip建模的好处是,代码和数学公式几乎一一对应,不容易写错。核心代码如下:
matlab复制%% 定义决策变量
% 第一阶段变量(日内确定,不受场景影响)
P_grid = sdpvar(1, para.T); % 购电功率
P_gt = sdpvar(1, para.T); % 燃气轮机出力
u_gt = binvar(1, para.T); % 燃气轮机启停状态
% 第二阶段变量(随场景变化)
P_ess_ch = sdpvar(para.Ns, para.T); % 储能充电
P_ess_dis = sdpvar(para.Ns, para.T); % 储能放电
SOC = sdpvar(para.Ns, para.T+1); % SOC状态变量,多一个维度便于初始化
H_gb = sdpvar(para.Ns, para.T); % 燃气锅炉供热
P_eb = sdpvar(para.Ns, para.T); % 电锅炉耗电
H_tss_ch = sdpvar(para.Ns, para.T); % 蓄热罐充热
H_tss_dis = sdpvar(para.Ns, para.T); % 蓄热罐放热
目标函数在Yalmip里可以直接累加求和,不需要自己写循环展开矩阵乘法,非常省事:
matlab复制%% 目标函数
Objective = 0;
for t = 1:para.T
% 购电成本(第一阶段变量,取期望场景的平均值近似)
Objective = Objective + para.price_buy(t) * P_grid(t);
end
for s = 1:para.Ns
for t = 1:para.T
% 燃气成本(燃气轮机+燃气锅炉)
V_gas = P_gt(t)/para.GT.eta_e + H_gb(s,t)/para.GB.eta;
Objective = Objective + para.pi(s) * para.price_gas * V_gas;
% 运维成本
Objective = Objective + para.pi(s) * (para.k_gt*P_gt(t) + para.k_eb*P_eb(s,t) + ...
para.k_ess*(P_ess_ch(s,t) + P_ess_dis(s,t)));
end
end
注意第一阶段变量不乘场景概率,第二阶段变量乘以概率权重,这是两阶段随机规划的标准写法,能保证目标函数在概率意义上是对各场景期望成本的正确计算。
4.4 约束条件写入方式
约束条件我用cell数组聚合,最后一次性放入约束集合:
matlab复制Constraints = [];
for t = 1:para.T
% 电力平衡约束(每个场景)
for s = 1:para.Ns
Constraints = [Constraints,
P_grid(t) + pv_scen(s,t) + wt_scen(s,t) + P_gt(t) * para.GT.eta_e + P_ess_dis(s,t) == ...
para.L_elec(t) + P_eb(s,t) + P_ess_ch(s,t)];
end
% 热力平衡约束
for s = 1:para.Ns
Constraints = [Constraints,
P_gt(t) * para.GT.eta_h + H_gb(s,t) + P_eb(s,t) * para.EB.COP + H_tss_dis(s,t) == ...
para.L_heat(t) + H_tss_ch(s,t)];
end
end
这里电锅炉用了COP(能效比)来换算电功率到热功率,不同设备的单位换算需要统一到MW。我最初在这个问题上吃过亏,热负荷单位是MWt、电负荷单位是MWe,写约束时把两个数直接相加,导致平衡约束怎么调都不闭合,检查半天才发现是单位问题。
4.5 储能SOC与设备约束
储能和蓄热罐的SOC约束需要按时间递推,用循环写法最直观:
matlab复制for s = 1:para.Ns
% SOC初始条件
Constraints = [Constraints, SOC(s,1) == para.ESS.SOC_init * para.ESS.Ecap];
for t = 1:para.T
% SOC递推
Constraints = [Constraints,
SOC(s,t+1) == SOC(s,t) + (P_ess_ch(s,t)*para.ESS.eta_ch - P_ess_dis(s,t)/para.ESS.eta_dis)];
% SOC上下限
Constraints = [Constraints,
para.ESS.SOC_min * para.ESS.Ecap <= SOC(s,t+1) <= para.ESS.SOC_max * para.ESS.Ecap];
% 充放电状态互斥(需要0-1变量)
u_ch = binvar(1,1); % 简化写法,实际应定义完整矩阵
Constraints = [Constraints, P_ess_ch(s,t) <= para.ESS.Pmax * u_ch];
Constraints = [Constraints, P_ess_dis(s,t) <= para.ESS.Pmax * (1-u_ch)];
end
% 循环约束(日始日末SOC相等)
Constraints = [Constraints, SOC(s,para.T+1) == para.ESS.SOC_init * para.ESS.Ecap];
end
这个简化写法引入了大量0-1变量。如果对求解时间敏感,可以用big-M方法改写成线性约束,或者直接用凸松弛——对于储能来说,由于充放电效率的存在,最优解天然不会同时充放电,因此可以把互斥约束去掉,只保留充放电功率非负和上下限约束。实际跑下来,去掉互斥约束后求解时间减少约40%,而最优解几乎不受影响。这算是一个“理论上有风险、实践上很安全”的加速技巧。
4.6 求解器调用与结果输出
Yalmip+求解器调用就三行:
matlab复制ops = sdpsettings('solver', 'cplex', 'verbose', 2, 'showprogress', 1);
result = optimize(Constraints, Objective, ops);
求解完成后检查结果有效性:
matlab复制if result.problem == 0
% 求解成功
P_grid_opt = value(P_grid);
P_gt_opt = value(P_gt);
P_ess_opt = value(P_ess_ch) - value(P_ess_dis);
% 绘制曲线
figure;
plot(1:24, P_grid_opt, 'linewidth', 1.5); hold on;
plot(1:24, mean(value(P_ess_opt),1), 'linewidth', 1.5);
legend('购电功率', '储能净出力');
else
% 求解失败,输出错误码
disp(['求解失败, problem code: ', num2str(result.problem)]);
end
Cplex的退出码需要经验积累:1表示infeasible,3表示unbounded,6表示数值问题。其中数值问题最隐蔽,通常表现为约束系数矩阵条件数过大,需要检查量纲或重新缩放变量。
5. 算例设置与结果分析
写代码的目的终究是出结果。这一节用一个小规模算例演示完整的输入输出,方便你验证自己的代码是否正确。
5.1 基础数据设置
为便于复现,我用一个简化算例:24小时,10个场景,设备容量按小型园区级设置。
| 参数 | 数值 |
|---|---|
| 电网购电上限 | 80 MW |
| 燃机容量 | 60 MW(电)/ 40 MW(热) |
| 燃气锅炉容量 | 30 MWt |
| 电锅炉容量 | 20 MWt |
| 电储能容量/功率 | 50 MWh / 15 MW |
| 光伏装机 | 40 MW |
| 风电装机 | 60 MW |
分时电价采用典型的峰谷电价结构:峰时(10:00-15:00, 18:00-21:00)1.2元/kWh,平时0.75元/kWh,谷时0.4元/kWh。天然气价格2.5元/m³,折算成热值约0.25元/kWh。
5.2 优化结果解读
求解得到典型日的调度计划后,我一般按三个维度分析结果:
- 各电源出力曲线是否合理:燃机基本在峰电时段出力最高,谷电时段压低或停机,储能谷充峰放。
- 热电平衡是否满足:尤其在电锅炉参与调峰后,夜间的弃风率如何变化。
- 不确定性的影响:10个场景下,储能出力曲线与确定性模型的差异是否明显。
用10场景随机优化时,总运行成本大约比确定性模型高6%到10%。这多出来的成本就是“为不确定性付出的保险费”——它保证了在最坏场景下系统仍能维持供需平衡。如果你把这个溢价讲给决策者听,他们就明白为什么要考虑不确定性。
5.3 场景数对结果的影响
我跑过一组对照实验,场景数从2个逐步增加到50个:
| 场景数 | 运行成本(万元) | 求解时间(秒) |
|---|---|---|
| 2 | 68.3 | 3.2 |
| 5 | 72.1 | 9.7 |
| 10 | 73.6 | 21.5 |
| 20 | 74.2 | 46.8 |
| 50 | 74.5 | 130+ |
结论很明显:当场景数少于10个时,结果对场景数非常敏感;超过10个后结果基本稳定,但求解时间近似线性增长。所以实际项目中,场景数月10~20个是性价比最高的区间。
6. 调试过程记录与实际运行问题排查
代码不是一次就能跑通的,这里把我调试过程中遇到的高频问题做个速查表,很多问题不看案例根本想不到。
6.1 求解失败的几种情况
| 错误信息 | 可能原因 | 排查方法 |
|---|---|---|
| infeasible problem | 约束过于严格,无可行解 | 先放松平衡约束,逐个加入;检查负荷单位 |
| unbounded problem | 目标函数中某些变量系数缺失或成本为负 | 检查目标函数,是否遗漏了变量或误设了负价格 |
| numerical issues | 数值尺度差异过大 | 归一化数据,把功率单位从W改为MW |
| 求解时间过长 | 0-1变量过多、场景数过多 | 去掉储能互斥约束、削减场景数、设置gap容差 |
最经典的案例是第一次跑通时,电网购电功率上限设成了80MW,但电负荷峰值本身就有90MW,结果模型直接infeasible。排查时花了不少时间,最后通过逐一删除约束再求解,才定位到是上限参数设置不合理。
6.2 结果不合理时的检查思路
如果模型能求解但结果出现“购电功率竟为负值”或“储能一直充满不放电”这类奇怪现象,优先检查:
- 目标函数中购电成本的符号是否正确。购电功率变量在平衡约束中是正号代表输入,若目标函数中也写了正号但价格是正的,算法会尽量少购电,甚至通过售电获利(把购电变量跑成负值)。
- 分时电价的方向是否和储能充放电逻辑一致。谷时电价低应充电、峰时电价高应放电,如果结果反了,检查SOC递推方向或充放电约束中的正负号是否颠倒。
- SOC初始值是否合理。如果日初SOC设为1.0(满电状态),储能几乎没有充电空间,调度结果当然偏“只放不充”。
6.3 求解时间的针对性优化
综合能源系统优化最容易让人急躁的就是求解时间。我的经验按优先级排序:
- 检查模型尺度:确保所有变量的数量级在同一个量级,相差超过1e6时Cplex的数值稳定性会大幅下降
- 减少整数变量数量:储能互斥约束的0-1变量是主要来源,考虑去掉或改为松弛
- 设置合理的求解精度:
sdpsettings('cplex.mip.tolerances.mipgap', 0.001),1%的gap对运行成本来说通常完全够用,但可以节省大量分支定界时间 - 尝试换求解器:同一个模型Gurobi和Cplex的性能在部分问题上差距显著,我有一次切换Gurobi后求解时间缩短了六成
7. 从单目标到多目标:扩展方向与思考
如果基础版本已经跑通,后续扩展有很多方向,我这里结合实际经验提几个我认为最有价值的。
第一个是考虑碳排放约束或碳交易成本。在目标函数中增加碳排放成本项,或者在约束中设置碳排上限,就能把“双碳”目标纳入优化。操作上,燃气轮机和锅炉的碳排放系数已经有了公认的参考值,加入后模型结构不变,只是目标函数多了一项。
第二个是引入需求侧响应。把部分可平移负荷、可中断负荷建模为可调变量,允许优化算法在电价偏高时减少负荷,这会改变负荷平衡约束的形式,从固定值变成决策变量。需求响应会让模型更接近真实系统,也更考验建模功底。
第三个是两阶段随机规划扩展。第一阶段决定机组启停、购电协议等“日前决策”,第二阶段根据日内实际出力做经济调度。这种模型更贴合电力市场的实际流程,但对应代码结构需要增加不少复杂性——主要在于区分哪些变量属于第一阶段,哪些属于第二阶段。
扩展时我的建议是每次只加一类复杂度。不要一次性又加碳排又加需求响应又改两阶段——那样模型一旦出问题,根本不知道是哪部分引入的bug。先跑通基准版本,再逐步叠加,每步都用表格记录成本和求解时间的变化。
从实践角度来说,综合能源系统协同优化这个方向,代码只是载体,核心还是对系统物理特性的理解和对不确定性量化方法的掌握。Matlab加Yalmip加Cplex这套组合,最大的价值是让你花最少的时间把数学模型变成可求解的算例,把精力集中在问题本身的洞察上。
最后分享一个我在多个项目里验证过的小技巧:拿到任何新的综合能源系统算例,先跑一个完全不考虑不确定性的确定性版本作为基准,再把不确定性加回去。对比两者的结果差异,就能非常直观地评估不确定性对调度方案的影响有多大,这个对比常常比最终的优化结果本身更有说服力,写论文、做汇报时也是很好的素材。
