ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

微电网日前调度优化实战:MATLAB与CPLEX求解MILP模型

2026/9/30 8:38:50 拓冰建站 浏览量
微电网日前调度优化实战:MATLAB与CPLEX求解MILP模型 微电网调度优化这几个字听起来像是研究生论文里的术语但落地到实际工程中无非就是回答一个问题未来24小时光伏、风电、储能、柴油机和主网各自该出多少力才能在满足负荷的同时把总运行成本压到最低。我最近用 MATLAB 与 CPLEX 把这套流程从头到尾跑了一遍从模型搭建、约束矩阵构造、求解器调试到结果曲线分析踩了不少坑也慢慢摸出一套可复用的实操路径。这篇文章就围绕“微电网日前调度优化 MATLAB 调用 CPLEX 求解 MILP”这个核心展开既讲清楚为什么要这么配也把关键代码和调试经验摊开来说。如果你正在做微电网经济调度、储能充放电策略优化或者刚入门想找一套能直接上手的求解框架都可以把这些内容当作参考。尤其适合那些模型公式能看懂、但一写代码就卡住的朋友其实从公式到稀疏矩阵再到求解器中间只差一层“翻译”工作。1. 项目背景与问题拆解1.1 微电网调度到底在优化什么微电网是一个小范围发输配用系统常见组成包括光伏、风机、储能电池、柴油发电机以及本地负荷和与主网的公共连接点。所谓“调度”就是调度中心根据预测的负荷曲线、光伏出力曲线和风电出力曲线提前决定每一个控制时段里各设备应该工作在什么状态。这里的控制时段通常取1小时也有取15分钟做日内滚动的但前调度以24小时为周期最常见。这个问题的数学本质是混合整数线性规划MILP。为什么不是简单的线性规划因为柴油机有“开机/停机”两种离散状态储能也有“充电/放电”两种互斥状态这些天然就是0/1变量。如果只用连续变量建模求解器很可能会给出一个看似合理、实际无法落地的方案比如柴油机在最小技术出力以下运行或者储能同一时段一边充电一边放电来赚价差。引入0/1变量之后模型才真正贴近物理约束。我习惯用一个类比来理解这件事微电网调度就像餐厅排班经理。柴油机是正式员工一开机就是4小时班不能随时甩手走人储能是兼职小时工有最大工时限制而且不能一边干活一边休息。排班经理要在成本最低的前提下把每个时段的任务分配清楚。这个排班问题就是MILP。1.2 为什么偏偏是 MATLAB 和 CPLEX 的组合我第一次做这类优化时先试了MATLAB自带的intlinprog小规模问题跑起来还挺顺畅但一旦24时段全部加上启停变量、储能互斥约束和SOC递推计算时间就开始失控。后来换成CPLEX同样的模型规模求解速度明显上了一个台阶。这个组合的“奇妙”之处在于MATLAB负责数据输入、矩阵组装、结果可视化CPLEX负责核心的分支定界、割平面、启发式搜索。CPLEX是商用级MILP求解器对整数规划的求解效率和稳定性确实不是优化工具箱里那个内置求解器能比的。当然Gurobi、SCIP等求解器也很强但CPLEX有个很实用优势是提供Community Edition免费社区版个人学习和中小规模项目完全够用。另外MATLAB里还有YALMIP这类建模工具能用符号变量写模型代码确实短。但我自己更推荐直接用原生API写稀疏矩阵。原因有两条第一符号变量在大规模模型下解析速度明显下降第二矩阵形式更透明出了问题可以直接查LP文件逐行核对约束排错效率高。1.3 项目整体技术路线整个项目我拆成四步走数据准备整理24小时负荷预测、光伏和风电预测、分时电价、设备参数建模定义连续变量和0/1变量写出目标函数和全部约束矩阵化把等式约束和不等式约束转成Aeq、beq、A、b并组装成稀疏矩阵求解与后处理调用CPLEX求精确解再把结果还原成功率曲线和成本曲线。这四步看起来平淡无奇但真正容易翻车的都在第3步。约束之间相互耦合少写一个等式或者符号写反模型就会无解多写一个大M约束解算时间又会暴涨。后面章节我会把每一步的关键细节展开讲。2. 目标函数与约束条件的完整推演2.1 目标函数总运行成本最小化日前调度最常用的目标函数是全时段总运行成本最小包含以下几块柴油机燃料成本柴油机启停成本从主网购电的成本减去向主网售电的收益储能充放电带来的电池退化成本。用公式表达就是min Σ_{t1}^{24} [ C_fuel(t) C_su(t) C_sd(t) C_grid(t) C_bat(t) ]其中柴油机燃料成本可以做线性近似C_fuel(t) a × P_dg(t) b × u_dg(t)这里P_dg(t)是柴油机出力u_dg(t)是开机状态的0/1变量a是边际燃料成本系数b是空载固定成本。如果机组燃料曲线是凸的这种线性近似已经足够如果燃料曲线是非线性的就需要引入分段线性函数做逼近。主网交互成本可以写成C_grid(t) p_buy(t) × P_buy(t) - p_sell(t) × P_sell(t)其中p_buy和p_sell分别是购电和售电电价P_buy和P_sell分别是购电功率和售电功率。正常情况下售电价会低于购电价目标函数会自动避免同一时段既购电又售电但如果你用的数据里出现倒挂最好还是加一个购售互斥约束。储能退化成本我建议一定要加。这个成本项本质上是给电池充放电行为一个“惩罚”不然求解器会把储能当成免费工具结果出现SOC曲线反复大起大落。虽然账面成本低了实际电池寿命损失却被忽略了。2.2 约束条件一个都不能少约束条件我列成一张速查表约束类型数学表达物理含义功率平衡PV VVT DG 放电 购电 负荷 充电 售电每个时段发用电必须瞬时平衡柴油机出力上下限u_dg × P_min ≤ P_dg ≤ u_dg × P_max开机时出力在技术范围内柴油机爬坡约束-RD ≤ P_dg(t) - P_dg(t-1) ≤ RU相邻时段出力变化受限储能充放电上限0 ≤ P_ch ≤ u_ch × P_ch_max0 ≤ P_dis ≤ u_dis × P_dis_max充放电功率不超过额定值充放电互斥u_ch u_dis ≤ 1不能同时充电和放电SOC递推SOC(t) SOC(t-1) η_ch×P_ch×Δt/Cap - P_dis×Δt/(η_dis×Cap)荷电状态动态变化SOC边界0 ≤ SOC(t) ≤ 1不超容量购售电上限0 ≤ P_buy ≤ P_buy_max0 ≤ P_sell ≤ P_sell_max受变压器容量和合同限制这里特别想提醒功率平衡约束的符号。光伏和风电在等式里通常作为“已知量”移到等式右边相当于负荷的一部分被新能源抵消了。储能充电是消耗功率的所以要放在负荷侧储能放电是提供功率的要放在电源侧。很多人第一次建模时容易把储能充电功率符号写反导致模型要么无解要么出现能量凭空产生。SOC递推约束是储能模型的核心。SOC(t)表示t时段结束时的荷电状态SOC(t-1)是上一时段结束时状态初始值SOC0需要给定。很多项目还会要求调度周期结束时SOC回到初始值这可以作为一个等式约束加上但注意这个约束会显著缩小可行域如果实际场景允许最好留一个松弛空间。2.3 案例参数设定为了让后面的代码有参照我把案例设成一个小型微电网所有功率单位统一用MW时间单位统一用小时h柴油发电机最小出力0.1 MW最大出力1.5 MW爬坡速率0.6 MW/h储能系统容量2 MWh最大充电功率0.5 MW最大放电功率0.5 MW充放电效率均取0.95初始SOC为0.5与主网交互购电上限1.0 MW售电上限0.5 MW分时电价谷时0.30元/kWh峰时0.90元/kWh平时0.50元/kWh简化成4个时段区间。24小时负荷和新能源预测数据在实际项目中应该来自历史数据和天气预报这里我直接用一段MATLAB代码生成示意序列重点不是数据本身而是后续的模型约束矩阵怎么构建。3. MATLAB 调用 CPLEX 的完整实操流程3.1 环境准备安装 CPLEX 并打通 MATLAB 接口MATLAB 调用 CPLEX 的第一步是安装求解器和添加接口路径。CPLEX Community Edition 可以从 IBM 官网免费下载安装时记得勾选 MATLAB 支持组件。安装完成后在 MATLAB 里添加对应的路径不同版本路径不一样我这里以 CPLEX Studio 22.1 为例addpath(C:\Program Files\IBM\ILOG\CPLEX_Studio2210\cplex\matlab\x64_win64); savepath;路径最后一般是x64_win64或x64_linux具体看你操作系统和安装目录。添加完路径后验证接口是否正常try cplex Cplex(); disp(CPLEX接口OK); catch ME disp(ME.message); end如果这一步报错最常见原因是路径没加对、MATLAB位数不是64位、或者安装目录里有中文和空格。我不建议把CPLEX装到带中文的路径下mex编译文件对这种路径很敏感。另外提醒一句Community Edition对模型规模有限制约束数、变量数和非零元总数都会有限制用来做学术演示和小型工程案例没问题但如果你的微电网节点很多、时段很长建议申请 Academic Initiative 版本或者购买正式授权。3.2 用原生 API 搭建 MILP 模型我习惯先把所有决策变量按照“分块索引”的方式组织起来。假设全天分为N24个时段变量向量x的结构如下x(1:N)柴油机出力x(N1:2N)柴油机开机状态0/1变量x(2N1:3N)储能充电功率x(3N1:4N)储能放电功率x(4N1:5N)充电状态0/1变量x(5N1:6N)放电状态0/1变量x(6N1:7N)购电功率x(7N1:8N)售电功率x(8N1:9N)SOC状态。先定义参数和索引N 24; dt 1; % 小时 % 将下面换成实际预测数据 P_load 0.4 0.2*sin((0:N-1)*pi/12) 0.05*randn(1,N); P_pv max(0, 0.5*sin((0:N-1)*pi/12)) .* ((0:N-1) 6) .* ((0:N-1) 18); P_wt 0.1 0.05*cos((0:N-1)*pi/24); % 柴油机参数 dg_min 0.1; dg_max 1.5; dg_ru 0.6; dg_rd 0.6; fuel_a 120; % 元/MWh fuel_b 35; % 元/h % 储能参数 ess_cap 2.0; ess_ch_max 0.5; ess_dis_max 0.5; eta_ch 0.95; eta_dis 0.95; soc0 0.5; % 主网价格分4段 p_buy [0.3*ones(8,1); 0.9*ones(6,1); 0.5*ones(4,1); 0.8*ones(6,1)]; p_sell p_buy * 0.7; % 售电价 buy_max 1.0; sell_max 0.5; % 变量索引 idx.dg 1:N; idx.u_dg N1:2*N; idx.ess_ch 2*N1:3*N; idx.ess_dis 3*N1:4*N; idx.u_ch 4*N1:5*N; idx.u_dis 5*N1:6*N; idx.buy 6*N1:7*N; idx.sell 7*N1:8*N; idx.soc 8*N1:9*N; nvars 9*N;这里用结构体idx存索引后面拼约束矩阵时就能直接写idx.dg(3)这类语义清晰的代码一旦模型规模扩大这种编码方式也不容易乱。目标函数向量和变量类型定义f zeros(nvars, 1); f(idx.dg) fuel_a; f(idx.u_dg) fuel_b; f(idx.ess_ch) 0.02; % 储能退化成本系数 f(idx.ess_dis) 0.02; f(idx.buy) p_buy; f(idx.sell) -p_sell; lb zeros(nvars, 1); ub inf(nvars, 1); ub(idx.soc) 1.0; ctype repmat(C, 1, nvars); ctype(idx.u_dg) B; ctype(idx.u_ch) B; ctype(idx.u_dis) B;这里有一个小细节售电收益在目标函数里是负成本因为售电是收入f(idx.sell)取负值即可。接下来是约束矩阵构建。先写等式约束功率平衡和SOC递推。Aeq sparse(2*N, nvars); beq zeros(2*N, 1); % 功率平衡约束前N行 for t 1:N Aeq(t, idx.dg(t)) 1; Aeq(t, idx.ess_dis(t)) 1; Aeq(t, idx.buy(t)) 1; Aeq(t, idx.ess_ch(t)) -1; Aeq(t, idx.sell(t)) -1; beq(t) P_load(t) - P_pv(t) - P_wt(t); end % SOC递推约束后N行 for t 1:N r N t; Aeq(r, idx.soc(t)) 1; if t 1 Aeq(r, idx.soc(t-1)) -1; end Aeq(r, idx.ess_ch(t)) -eta_ch * dt / ess_cap; Aeq(r, idx.ess_dis(t)) dt / (eta_dis * ess_cap); if t 1 beq(r) soc0; else beq(r) 0; end end不等式约束可以用一个大矩阵Aineq加一个列向量bineq来组装。以柴油机出力上下限和储能充放电上限为例Aineq sparse(2*N, nvars); bineq zeros(2*N, 1); % 柴油机出力上限P_dg - P_max*u_dg 0 % 出力下限P_min*u_dg - P_dg 0 row 0; for t 1:N row row 1; Aineq(row, idx.dg(t)) 1; Aineq(row, idx.u_dg(t)) -dg_max; bineq(row) 0; row row 1; Aineq(row, idx.dg(t)) -1; Aineq(row, idx.u_dg(t)) dg_min; bineq(row) 0; end储能充放电上限、充放电互斥、购售电上限、爬坡约束同样往Aineq和bineq里追加即可。这里我就不再重复粘贴核心思路是每追加一类约束就增加对应的行最后统一传给CPLEX。组装模型并求解Aall [Aeq; Aineq]; lhs [beq; -inf(size(Aineq,1), 1)]; rhs [beq; bineq]; cplex Cplex(microgrid_scheduling); cplex.Model.sense minimize; cplex.Model.obj f; cplex.Model.lb lb; cplex.Model.ub ub; cplex.Model.ctype ctype; cplex.Model.A Aall; cplex.Model.lhs lhs; cplex.Model.rhs rhs; cplex.write(microgrid_model.lp); % 导出LP文件便于调试 cplex.solve();求解完之后检查返回状态并提取结果if cplex.Solution.status 101 || cplex.Solution.status 102 x cplex.Solution.x; P_dg_out x(idx.dg); U_dg_out x(idx.u_dg); P_ch_out x(idx.ess_ch); P_dis_out x(idx.ess_dis); SOC_out x(idx.soc); P_buy_out x(idx.buy); P_sell_out x(idx.sell); else disp(求解失败检查模型是否可行); endCPLEX里状态码101表示找到最优解102表示在时间限制内找到可行解通常也够用了。3.3 结果分析与曲线绘制结果出来之后我通常会画两张图第一张是功率平衡堆叠图看每个时段的电源构成和负荷曲线第二张是储能SOC曲线看储能行为是否合理。t_h 0:N-1; figure; bar(t_h, [P_pv(:), P_wt(:), P_dg_out(:), P_dis_out(:), P_buy_out(:)], stacked); hold on; plot(t_h, P_load(:), k-, LineWidth, 2); xlabel(时段/h); ylabel(功率/MW); legend(光伏,风电,柴油机,储能放电,购电,负荷); figure; stairs(t_h, SOC_out, LineWidth, 1.5); xlabel(时段/h); ylabel(SOC); ylim([0 1]);典型结果会出现几个规律夜间光伏为零、负荷较低储能在谷电时段充电白天光伏大发、负荷上升储能放电配合削峰柴油机往往只在负荷高峰或者新能源出力不足的时段启动。如果电价峰谷差够大储能的“低充高放”套利行为会非常明显这也是微电网调度中最直观的经济性体现。4. 常见问题与排查技巧实录4.1 模型报 infeasible 怎么办模型无解是我遇到的第一个大坑。最常见的原因有三个功率平衡约束符号写反、SOC初值终值条件互相矛盾、柴油机最小出力太大导致某些时段无论如何都平衡不了。排查时我最推荐的办法是把LP文件导出然后在CPLEX命令行工具或者用文本编辑器直接打开microgrid_model.lp逐行检查等式约束c1: dg_1 ess_dis_1 - ess_ch_1 buy_1 - sell_1 0.1如果发现常数项符号不对就能在矩阵层面快速定位。还有一种方法是把等式约束先放宽成不等式比如功率平衡允许小范围偏差看模型是否可行如果放宽后可行说明等式内部逻辑有冲突。4.2 求解时间太长怎么办当整数变量增多、约束规模上来之后求解时间确实会拉长。我有三个常用手段第一设置MIP gap和求解时间上限cplex.Param.timelimit.Cur 300; cplex.Param.mip.tolerances.mipgap.Cur 0.01;允许1%的次优性工程上通常是可以接受的求解时间能下降一个量级。第二检查大M参数。储能充放电上限约束里M值不需要取太大直接取最大充放电功率0.5 MW就是最紧的取值。很多人习惯写一个很大的M1000这会让LP松弛质量变差分支定界效率急剧下降。第三减少不必要的整数变量。比如把充电和放电状态变量去掉改用“功率分段”的方式建模虽然还是MILP但变量结构更简洁求解速度也会快一些。4.3 MATLAB 和 CPLEX 版本对不上怎么办CPLEX每个版本对MATLAB的官方支持范围不完全一样新MATLAB版本刚出来时旧CPLEX的mex文件可能加载失败。解决办法有三个升级CPLEX、降级MATLAB版本、或者检查路径里是不是同时存在多个CPLEX版本。如果报错信息类似Unable to resolve the name cplex.Cplex绝大多数情况是路径没有正确添加。运行一次ver(cplex)或者which Cplex.m就能看出来到底有没有加载成功。如果安装目录有中文或空格建议卸载重装一次路径问题真的不值得花几个小时去折腾。4.4 结果曲线出现抖动和不合理跳变模型求解成功不代表结果合理。我见过最典型的“假最优解”是储能SOC曲线呈现出高频锯齿形状每15分钟一个充电/放电切换目标成本上显示很漂亮实际运行根本执行不了。这个问题主要是储能退化成本系数设得太小求解器觉得频繁操作电池无成本所以不心疼。解决方法是调大退化成本系数或者加上最小充放电持续时间约束。另一个常见问题是购电和售电同时为正。只要购电价高于售电价目标函数会自动避免但如果你的数据是平段和谷段价格倒挂就需要加一个0/1互斥约束u_grid_buy u_grid_sell ≤ 1保证同一时段不会既买又卖。5. 模型扩展方向与个人沉淀的经验5.1 从确定性模型走向不确定优化前面这套模型假设光伏、风电、负荷预测都是准确的实际工程中预测误差不可避免。更进阶的做法是引入两阶段随机优化或鲁棒优化。MATLAB的优势在于可以方便地生成多个场景把每个场景对应的一组功率平衡约束同时塞进模型CPLEX依然能够求解。虽然模型规模和求解时间会上升但对真实微电网运行来说这种不确定性建模的价值非常直接。5.2 与强化学习结合做在线调度现在MATLAB里实现DQN、PPO等强化学习算法已经很常见。很多人问我是不是学了强化学习就可以替代CPLEX做调度。我的看法是确定性日前调度问题用MILP和CPLEX求解速度快、有全局最优保证根本没有必要用强化学习硬碰强化学习更适合在线决策、模型非线性强或者状态空间部分可观测的场景。如果你想做对比实验可以先用这个MILP结果作为基准再训练一个DQN或者PPO智能体看在线调度策略和离线最优解之间差多少这个方向很有意思。5.3 几个值得长期保留的操作习惯做完这个项目后我给自己定了三条规矩分享给你第一每次求解前都导出LP文件哪怕是已经优化好的模型也导出一次一秒钟的事关键时刻能救命。第二功率单位全部统一成MW、时间单位统一成h公式里到处是dt和效率单位混了再好的模型也算不对。第三每个场景的参数和结果都存成MAT文件加load就能复现后续改模型、调参数、做对比都方便。这三个习惯看起来不起眼但帮我省下了大量复查和返工时间。模型做完之后我还会专门检查一次SOC曲线是否平滑、柴油机启停次数是否合理这两个指标比单纯看总成本更能反映模型质量。微电网调度的奇妙之处就在于把复杂的物理系统翻译成一组线性约束再用好的求解器把它解开而MATLAB与CPLEX正是这套翻译链路上非常顺手的一对搭档。