☰
混合整数线性规划求解机组组合:MATLAB+YALMIP+CPLEX实战与热备用率影响
2026/10/1 16:40:18 网站建设 项目流程

简介:面向电力系统机组组合问题,这是一套基于混合整数线性规划的MATLAB完整实现方案,适合电力系统调度初学者、电气专业高年级学生以及需要搭建优化模型的算法工程师。资源核心涵盖机组启停状态的0/1整数变量、出力连续变量、燃料成本最小化目标,以及功率输出、电网稳定等典型约束;同时给出YALMIP建模与Cplex求解的完整流程。压缩包内共7个文件,包含3个Excel结果表格、2个Visio最优出力图表、1份Word说明文档和1个MATLAB脚本,分别用于数据记录、结果可视化、基本要求说明与核心求解逻辑,总体仅267KB,结构紧凑。已有3051人学习该资源,通过0.05与0.2两种热备用比例的对比结果,可直观理解备用容量对机组组合方案的影响,并据此掌握电力系统机组组合的建模、求解与结果分析全流程,适合作为课程设计及科研参考。

1. 机组组合为什么要用混合整数线性规划:从“开几台机”到“0/1变量”的必然

做电力调度的人对机组组合都不陌生:明天负荷预计多少、哪些机组开机、每台发多少出力,既要把电供上,又要把成本压下来。这个问题的麻烦在于,发电机不是连续可调的旋钮,而是“开/关”这种离散动作——一台60万kW的火电机组,要么按技术出力下限带负荷,要么干脆停机。离散动作没法用普通线性规划处理,于是混合整数线性规划(MILP)成了工程上的标准解法:0/1整数变量管开停,连续变量管出力,燃料成本和启停成本一起进目标函数。这份资源包的实战价值就在这里,它把完整MILP机组组合模型写成MATLAB代码,用YALMIP建模、CPLEX求解,还附带了热备用率0.05和0.2两套场景的最优出力结果和Excel表格,很适合正在做电力系统优化调度课设、毕设,或者刚接手调度计划算法的工程师。

2. 看懂这个机组组合模型:决策变量、目标函数与那六类约束

2.1 变量设计:为什么u是二值变量而p是连续变量

MILP机组组合的建模,核心是把“启停”和“出力”两类决策拆开。u_i,t表示第i台机组在t时段是否开机,取值只能是0或1,这是整数变量;p_i,t表示第i台机组在t时段的出力水平,取值在技术最小出力和最大出力之间,这是连续变量。CPLEX在求解时对整数变量做分支定界,对连续变量做单纯形或内点法,两类变量协同优化的效率就取决于模型规模。

用YALMIP声明变量时,常见做法是:

u = binvar(nGen, nHours, 'full'); % 机组启停状态,0/1变量 p = sdpvar(nGen, nHours, 'full'); % 机组出力,连续变量

binvar声明二值变量数组,sdpvar声明连续变量数组。这里'full'参数指定矩阵是满结构而不是对称结构,避免YALMIP默认把方阵按对称矩阵处理——很多新手在这里翻车:声明变量时没加'full',约束矩阵维度对不上,CPLEX直接报错。两个变量维度都是nGen行、nHours列,行对应机组编号,列对应时段编号,后面所有约束都围绕这两个矩阵展开。

2.2 目标函数:燃料成本与启停成本的权衡

目标函数要体现调度员“省钱”的逻辑:运行成本主要是燃料成本,加上机组启动时的额外消耗。燃料成本和出力之间通常用二次曲线拟合,但MILP处理非线性麻烦,工程上常用的近似做法是把成本曲线分段线性化,或者直接简化成线性函数。这个案例代码里的目标函数,一般长这样:

Cost = 0; for t = 1:nHours for i = 1:nGen Cost = Cost + a(i) * p(i,t) + b(i) * u(i,t); % 运行成本:可变成本+固定成本 Cost = Cost + startupCost(i) * max(0, u(i,t) - u(i,t-1)); % 启动成本 end end optimize(Constraints, Cost);

a(i)是可变成本系数,b(i)是空载成本系数,startupCost(i)是单次启动费用。启动成本项写成max(0, u(i,t) - u(i,t-1))的意图很直白:只有机组从停机变开机的那一刻才计费,连续运行不重复收费。YALMIP对max非光滑函数会自动做模型重构,但实际工程中更稳妥的写法是把启动成本展开成专门的二进制变量,避免引入非线性表达。这里的简化写法用于教学和课设完全够用,实际调度系统里会拆细。

2.3 约束条件:负荷平衡、备用容量、爬坡、最小启停时间

约束是机组组合的骨架。缺了任何一类,求解结果在真实电网里都跑不起来。这套资源里涉及的核心约束可以归纳为以下六类,按优先级排列:

约束类型数学表达作用
功率平衡所有开机机组出力之和 = 负荷硬约束,任何时候必须满足
旋转备用开机机组最大出力之和 ≥ 负荷 + 备用保证突发故障时有富余容量
出力上下限技术最小出力 ≤ p ≤ 最大出力机组物理运行区间
爬坡约束|p(t) - p(t-1)| ≤ 爬坡速率机组出力不能跳变
最小启停时间开机后必须持续运行若干小时避免频繁启停损伤设备
启停逻辑u=0时出力必须为0停机机组不能发电

热备用率0.05和0.2这两个参数,在代码里对应旋转备用约束的系数。热备用0.05意味着系统要预留5%的容量裕度,0.2则是20%。这个参数从0.05调到0.2,看起来只是数字变了,但求解结果会发生质变——之前处于开机边缘的机组可能被强制开机,成本显著上升,这就是后文会展开的分析重点。

3. 把模型写进MATLAB:YALMIP建模与CPLEX求解的完整流程

3.1 数据组织:从Excel表格到MATLAB变量

拿到这个资源包,第一步不是跑代码,而是弄懂数据怎么进来的。Excel文件里存的是机组参数和负荷曲线,MATLAB代码通过xlsread读取,代码开头的数据结构大概是这样的:

% 读取机组参数和负荷数据 [num, txt, raw] = xlsread('jizuzuheyouhua.xlsx', '机组参数'); nGen = length(txt) - 1; % 机组台数,减去表头 genData = num; % [最大出力, 最小出力, 爬坡率, 启动成本, ...] Load = xlsread('jizuzuheyouhua.xlsx', '负荷曲线'); % 24小时负荷,单位MW Load = Load(:)'; % 转成行向量,便于索引

xlsread返回的num是数值矩阵,txt是文本单元格,raw是原始混合数据。注意读出来的负荷列向量要转成行向量,否则后面构造约束时维度对不上——YALMIP对维度不匹配会报“Inner matrix dimensions must agree”,这是最常见的第一个报错。

读取后建议加一行校验:

assert(length(Load) == nHours, '负荷数据长度和时段数不一致');

这种防御式写法在课设答辩时也是加分项,评审老师一眼就能看出你有工程意识。

3.2 构造约束矩阵:for循环拼接与向量化

机组组合的约束数量等于机组数乘以时段数,24个时段、10台机就是240组约束。Matlab里最直观的写法是双层for循环逐条写约束,代码可读性强,但求解效率会受影响。实际工程中更推荐向量化写法,不过教学代码为了让大家看懂,for循环是主流。这套资源代码里的约束构造逻辑,我一般会按下面这个模板组织:

Constraints = []; for t = 1:nHours % 功率平衡约束:所有在运机组出力之和 = 该时段负荷 Constraints = [Constraints, sum(p(:,t)) == Load(t)]; % 旋转备用约束:在运机组最大可用出力 ≥ 负荷 + 热备用 Constraints = [Constraints, sum(u(:,t) .* Pmax) >= Load(t) + reserveRate * Load(t)]; % 出力上下限约束:停机机组出力为0,开机机组在上下限区间 Constraints = [Constraints, p(:,t) >= Pmin .* u(:,t)]; Constraints = [Constraints, p(:,t) <= Pmax .* u(:,t)]; end

这里reserveRate就是热备用率,0.05或0.2。注意Pmin .* u这个写法很关键:当机组停机时u=0,约束变成p≥0;机组开机时u=1,约束变成p≥Pmin。这样一条约束同时实现了“出力下限”和“停机不出力”两个逻辑,是MILP建模里非常经典的技巧。

爬坡约束需要关联相邻时段,从第2个时段开始构造:

for t = 2:nHours % 爬坡约束:出力的时段间变化量不超过爬坡速率,且单位是MW/h Constraints = [Constraints, p(:,t) - p(:,t-1) <= RampUp * (ones(nGen,1) - u(:,t-1)) + Pmax * u(:,t-1)]; Constraints = [Constraints, p(:,t-1) - p(:,t) <= RampDown * (ones(nGen,1) - u(:,t-1)) + Pmin * u(:,t-1)]; end

爬坡约束里有个容易忽略的细节:机组停机后再启动,出力从0直接跳到某个值,物理上允许“热启动快速带负荷”,所以约束表达式里加入了启停状态的松弛项。如果不加这个松弛处理,模型会过于保守,甚至直接无解。这里的写法是工程上的标准处理——把爬坡限制只施加在连续运行时段上。

3.3 求解器配置:让CPLEX稳定跑出全局最优

YALMIP只是一个建模层,真正干活的是底层的CPLEX。求解器配置这一段,代码里通常是这样:

options = sdpsettings('solver', 'cplex', 'verbose', 2, 'showprogress', 1); options.cplex.mip.tolerances.mipgap = 0.0001; % 最优性间隙阈值 options.cplex.mip.tolerances.integrality = 1e-6; % 整数变量容差 options.cplex.timelimit = 300; % 求解时间上限,单位秒 sol = optimize(Constraints, Cost, options);

mipgap是核心参数,它定义求解器可以接受的次优程度。设成0.0001意味着CPLEX会一直搜索直到找到和最优解的差距在0.01%以内的解。如果设成0.05,求解速度会快不少,但结果可能离真正最优有5%的偏差——在电力系统这种对成本敏感的领域,5%的偏差可能就是几百万的电费差异。

求解完成后,YALMIP会把状态信息返回在sol结构体里。sol.problem等于0表示求解成功,非零值需要对号入座查错误文档,常见的有1表示求解器内部错误、2表示模型不可行、3表示无界,等等。我习惯在求解结束后立即检查:

if sol.problem ~= 0 disp('求解失败,错误码: ' + string(sol.problem)); return; end

很多事故都是没查这个返回值,拿到一个“最优解”就开始出调度单,实际那个解可能是不可行的。

3.4 结果提取:从YALMIP变量到可视化图表

求解完成后,value()函数提取变量数值:

u_opt = value(u); % 每台机组每个时段的启停状态 p_opt = value(p); % 每台机组每个时段的出力

拿到这两个矩阵,就可以做最基本的可视化——画机组出力堆叠图和系统总负荷曲线。资源包里附带的.vsdx文件就是已经画好的机组最优出力图,分热备用0.05和0.2两个场景。自己用代码画图也很简单:

figure; bar(p_opt', 'stacked'); % 堆叠柱状图,每根柱代表一个小时的负荷分配 hold on; plot(Load, 'r-', 'LineWidth', 1.5); % 红色实线叠加负荷曲线 xlabel('时段 (h)'); ylabel('出力 (MW)'); legend('机组1', '机组2', '负荷曲线', 'Location', 'northwest');

堆叠图能直观看出每个时段哪些机组在带基荷、哪些在调峰,后面分析热备用参数影响时,这张图是最有力的证据。

4. 热备用0.05与0.2:参数收紧后系统发生了什么

4.1 热备用率的作用机制:旋转备用如何改变机组组合形态

热备用率从0.05升到0.2,约束条件的右边项变大——同样是1000MW的负荷,0.05时要求开机机组至少能发1050MW,0.2时则要求至少能发1200MW。这个变化直接推着模型去开更多的机组,或者把部分机组压在高出力区间。用这份资源做对比实验时,可以观察到一个典型现象:热备用0.2场景的机组组合里,5号机组这类中等容量机组会被强制开机,哪怕它在0.05场景下是停机的。

为什么?备用约束的数学本质是让“开机机组的最大出力之和”大于一个阈值。当阈值提高,边际机组的收益(提供的备用容量)超过成本(燃料+启动消耗)时,模型最优解里就会多开一台机。这个边际判断是机组组合优化的核心逻辑,也是实际电力市场中容量电价和辅助服务补偿的定价基础。

4.2 档位对比结果:两个参数下的成本与出力结构差异

从资源包附带的Excel求解结果看,热备用0.05和0.2两个场景的差异相当明显。0.05场景下系统运行成本低,机组启停次数少,有大机组在低谷时段停机;0.2场景下启动成本明显上升,部分机组几乎全天在线,负荷低谷时段都只能压到技术出力下限运行,处于“低负荷空转”状态。

两个场景的机组出力对比如下表(以典型10机系统为例):

对比维度热备用0.05热备用0.2
单日总运行成本基准值(较低)约上升8%~15%
高峰时段开机机组数6~7台8~9台
低谷时段开机机组数3~4台5~6台
机组启停次数3~4次1~2次
边际机组多为小容量快速机组中容量机组被迫常开

这个对比表说明一个问题:热备用率不是越高越好。20%的备用让小机组空转烧煤,大机组虽然提供了足够的旋转备用,但经济性显著恶化。实际调度中,热备用率通常根据电网规模和电源结构设定在5%~10%之间,新能源渗透率高的系统要额外考虑爬坡能力需求。

这里的分析也解释了为什么资源包里要同时放两个场景的图表——没有对比就看不到机组组合优化的本质:它本质上是一个“多花多少钱买多少可靠性”的权衡问题。

4.3 参数化分析:批量跑不同备用率时怎么做

想在自己的机器上复现这个分析,甚至可以做一个简单的参数扫描——把热备用率从0.05到0.2每隔0.025跑一遍,看成本和开机模式的连续变化趋势:

reserveRates = 0.05:0.025:0.2; totalCosts = zeros(size(reserveRates)); for k = 1:length(reserveRates) reserveRate = reserveRates(k); % 在这里重新构建约束并求解,记录总成本 totalCosts(k) = value(Cost); end plot(reserveRates, totalCosts, 'o-'); xlabel('热备用率'); ylabel('总运行成本');

这种做法能把“备用成本曲线”完整体现出来——通常这条曲线在低备用区间平缓,高备用区间陡峭,拐点处对应经济性和可靠性的最佳平衡点。这也是电力市场机制设计里很有价值的一类分析。

5. 避坑记录:机组组合MILP求解的六个高发问题

5.1 模型不可行(Infeasible):约束之间打架了

  • 现象:CPLEX返回infeasible,sol.problem为2,YALMIP提示“No feasible solution found”。
  • 原因:最常见的是功率平衡约束左右两边数量级不对——负荷数据单位是MW,但机组容量参数单位写成了kW;或者爬坡约束对停机机组处理不当,导致天真的约束把可行域压成了空集。另一个高频原因是启动成本和启停逻辑约束自相矛盾——模型算出来机组先动后停再动,物理上不允许,数学上不可行。
  • 解决:先把约束分块注释掉,逐类排查。我一般先只保留功率平衡约束,求解看看是否可行;可行了再加备用约束,以此类推。排查时也可以用sol.info里的诊断信息定位具体卡在哪个约束上。另外,检查数据单位统一性,确认负荷和出力都用MW。

5.2 求解时间爆炸:MIPGap调优和初始解的作用

  • 现象:模型规模不大(几台机组、24时段),但CPLEX跑了十分钟还不停,日志里gap一直卡着不动。
  • 原因:MILP的求解复杂度最坏情况下随整数变量数量指数增长,即使只有几十台机组,没有好的初始解时,CPLEX也需要大量分支才能收敛。很多时候瓶颈在冗余约束——大量重复不等式拖慢了松弛模型的求解速度。
  • 解决:第一招是提供初始可行解,用启发式方法(如优先顺序法)先算一个开机方案,通过x0传给YALMIP;第二招是放宽MIPGap到0.01甚至0.05,接受千分之一的次优代价换来求解速度;第三招是给模型加对称性破缺约束——相同容量的机组之间强制排序,比如要求u(1,t) >= u(2,t),避免求解器在对称的分支里来回搜索。

5.3 冷启动报错:MATLAB路径和工具箱版本不匹配

  • 现象:运行时提示Undefined function or variable 'binvar',或者Error using sdpvar...。
  • 原因:YALMIP没有正确安装到MATLAB路径中,或者YALMIP和CPLEX的版本不兼容。YALMIP是纯M文件工具箱,安装时只需要把文件夹加入路径;CPLEX则依赖IBM的完整安装及其自己的MATLAB接口。
  • 解决:第一责任人通常是没把YALMIP的根目录和子目录都addpath进去;第二责任人是用savepath保存路径,这样重启MATLAB后依然有效。检查版本的组合,推荐YALMIP使用最新版(GitHub上持续维护),CPLEX用IBM ILOG CPLEX Optimization Studio安装时自带的MATLAB接口——配好了之后,在MATLAB命令行敲yalmiptest可以验证安装是否有问题。

5.4 数值警告:小参数引发的大误差

  • 现象:求解完成但sol.problem有警告,或者结果明显不合理——某台机组出力是1e-3,另一台是1000MW,看起来不协调。
  • 原因:模型里数值量级跨度过大。成本系数可能是每MW 0.05美元,启停成本可能是几万美元,差了好几个数量级,求解器内部的容差设定无法同时满足所有约束的精度要求。
  • 解决:对模型做归一化处理,把功率基准设为100MW,所有机组出力和负荷除以基准值;成本数据也统一除以基准值。这个工程细节能显著提升求解稳定性,跑大规模系统时几乎是必须做的一步。

5.5 Excel数据读取错位:xlsread返回NaN

  • 现象:读取Excel表格后,genData矩阵里有NaN,导致约束里出现NaN传播,最终求解器直接报“Model contains NaN”。
  • 原因:Excel表格里有合并单元格、空行或者非数字字符(比如“机组1”前面带了空格),xlsread把这些单元格解析成NaN。
  • 解决:读取前把Excel数据清理成纯数值表,表头只保留一行,数据区从第一个数值开始。读取后做一次any(isnan(genData(:)))检测,及时发现。或者改用readmatrix(新版MATLAB推荐),对数值类Excel表的解析更稳。

5.6 结果不合理:成本为负或者出力越限

  • 现象:求解成功,但总成本是负值,或者某台机组的出力超过了Pmax。
  • 原因:负成本通常来自目标函数里符号写反——成本参数前漏了负号。出力越限则可能是约束矩阵拼接时出现了行错位,把原本约束第2台机组的式子和第3台机组拼到了一起。
  • 解决:逐条检查成本系数的符号和数值;约束部分可以提取dual做灵敏度分析,确认哪些约束在起作用。更快的做法是写一段事后校验代码,专门对结果做规则检查——检查所有时段功率平衡是否成立、每台机组出力是否在物理范围内、启停逻辑是否矛盾,这个校验脚本在实际调度系统上线前是必须的。

6. 让结果能落地:从最优出力表反推调度单的备用分配技巧

求解器给出的最优出力矩阵,在真实调度中还不能直接下发。原因很简单:机组组合优化是基于预测负荷的静态计划,而实际运行中负荷永远在波动。面对这份资源的输出结果,真正有工程价值的技巧是把“备用”从总量细化成“按机组分配”的可调区间。

具体做法是把CPLEX算出来的每台机组出力作为基准点,结合机组爬坡速率定义上下调节范围,形成调度可执行区间。以某台60万kW机组为例,最优解中出力是50万kW,爬坡速率是每分钟2万kW,那么未来15分钟内的可调区间就是48万到52万kW。把所有机组的可调区间叠加,得到的就是系统在未来时段的动态备用能力曲线——比一次性的总备用数字更有指导意义。

我在做这类项目时,强制自己走一遍这个验证流程:求解完成后,先把每个时段所有开机机组的Pmax之和算出来,再减去该时段负荷,得到的差值必须大于等于热备用容量,误差在1MW以内才算通过。然后按30分钟为一个窗口,校验每台机组的相邻时段出力差是否在爬坡能力范围内。这些校验最好固化成一个脚本,每次跑完求解器自动执行——从那以后我每次面对新的机组数据,都先跑数据完整性检查,再跑求解,最后跑结果校验,三步缺一不可。这套习惯帮我挡掉过不少因为Excel数据少填一行、约束参数写错一位而导致的调度单翻车事故,希望也能帮到你。

本文还有配套的精品资源,点击获取

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

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

立即咨询