简介针对带储能的微电网优化调度问题这份代码包提供了一套基于MatlabYALMIPCPLEX的完整实现方案。内容聚焦混合整数线性规划的建模与求解适合电气工程、能源优化方向的学生、研究人员及工程师参考学习。压缩包仅含1个m文件大小约2KB结构精简便于直接运行、调试和二次开发。方案通过YALMIP高级建模语法定义目标函数与各类约束借助CPLEX求解器获取全局最优调度结果涵盖了发电机出力分配、储能充放电策略、功率平衡约束以及可再生能源接入等关键环节。资源已有3385人学习下载实用性较强。读者可从中掌握微电网优化调度问题的数学建模思路、YALMIP与CPLEX的联合调用方法以及储能系统约束的典型处理方式对开展相关科研课题、课程设计或工程实践具有明确的借鉴意义有助于理解运行成本与供电可靠性之间的权衡。1. 微电网优化调度先搞清楚这套 MatlabYALMIPCPLEX 代码到底帮你解决什么带储能的微电网优化调度说白了就是在满足负荷需求的前提下决定每一台柴油机发多少电、储能什么时候充什么时候放、能不能把弃掉的分布式能源省下来。这个问题数学上不算难但真正动手写约束时容易栽跟头——功率平衡、储能 SOC 连续变化、机组启停耦合随便哪条写不对CPLEX 给出的解就没法落地。这份资源是已经搭好的 MATLAB 工程核心文件 microgrid_Cplex.m 用 YALMIP 建模、CPLEX 求解覆盖了从目标函数到储能约束的完整调度流程。适合做微电网方向课程设计、毕业论文以及刚接触优化调度但不想从零造轮子的从业者。你拿到手能直接改参数跑结果省掉写约束和调求解器的最大一段弯路。2. 为什么是 Matlab YALMIP CPLEX三层分工决定了这套代码的上限2.1 三层工具链各自负责什么微电网调度在数学上是一个混合整数线性规划问题这里面有连续变量各机组出力、储能充放电功率也有整数变量机组启停状态、储能充放状态。求解这类问题的标准做法是建模语言负责把数学表达式写成求解器能认的格式求解器负责在可行域里找最优解而 Matlab 负责数据处理和结果可视化。这套资源里三个工具的分工非常明确Matlab承担数据准备、约束组装和结果后处理。比如读入风机和光伏的预测出力曲线、负荷曲线求解完成后把各时段出力画成曲线。没有 MatlabYALMIP 和 CPLEX 就只是个求解内核没法做完整的调度闭环。YALMIP干的是建模的活。它把「目标是最小化运行成本」翻译成optimize()能处理的标准型把sdpvar定义的连续变量和binvar定义的 0-1 变量打包成求解器能认的矩阵。它的价值在于你不用手动写成 CPLEX 的 LP 格式或 MPS 格式约束用Constraints [Constraints, ...]一条条拼就行。CPLEX负责真正求最优解。它内部用分支定界法处理整数变量用单纯形法或内点法处理线性松弛子问题。YALMIP 把模型传给它之后它会返回每个变量的取值和最优目标值。2.2 这套组合的适用边界在哪里不是所有微电网调度问题都适合用 CPLEX 硬解。这套组合擅长的是线性或混合整数线性规划也就是目标函数和约束里没有变量与变量相乘、没有非线性函数的情况。储能效率通常建模成常数机组成本通常写成分段线性或一次函数这两条在工程上都成立所以 MILP 是微电网调度的主流建模方式。那什么时候不该用这套组合如果模型里加入了潮流约束电压和相角的非线性关系、或者把储能效率建模成 SOC 的二次函数那问题就变成了非线性规划或混合整数非线性规划。CPLEX 对这类问题支持很弱YALMIP 会把模型转给别的求解器这时候这套代码就不适用了。另外如果机组数量和调度时段特别多比如上百台机组、上千个时段MILP 的求解时间会急剧上升CPLEX 虽然快但也扛不住天文数字的整数组合。判断一个调度问题适不适合这套资源就两个问题目标函数是不是线性的约束里有没有变量乘积答案都是否那这套代码可以放心用。有一个是否就得重新考虑建模方式。2.3 YALMIP 接口的底层逻辑YALMIP 对 CPLEX 的调用是透明的你不需要在代码里写任何 CPLEX 专属语法只需在 YALMIP 里定义变量和约束然后调用optimize(Constraints, Objective, options)。options里可以指定使用哪个求解器也可以设求解精度和最大求解时间。% 设置求解器选项指定 CPLEX并限制求解时间 options sdpsettings(solver, cplex, verbose, 2, ... cplex.timelimit, 300, ... cplex.mip.tolerances.mipgap, 0.001); % verbose2 表示打印求解日志timelimit 300 秒MIP gap 控制在 0.1% 以内这里cplex.timelimit控制最长求解时间超过后返回当前最好的可行解。mipgap控制最优性证明的容忍度设得太小比如 0在大规模问题上可能半小时都证明不了最优。工程上把它设在 0.001 到 0.01 之间既能保证解的品质又不会让求解器死磕。YALMIP 在调用 CPLEX 前会把约束矩阵做预处理把等式约束、不等式约束、整数约束分门别类地转换成 CPLEX 的 API 格式。这一层对用户不可见但带来的实际效果是你写的约束是什么形式CPLEX 收到的就是什么形式中间没有额外损耗。这也是 YALMIP 比直接在 C 里调 CPLEX 接口快得多的原因——开发时间省了求解效率也没牺牲。3. 调度模型怎么搭目标函数、功率平衡和储能约束是三个核心块3.1 目标函数到底在优化什么微电网优化调度的目标一般是「最小化系统总运行成本」这里要拆解清楚哪些成本可以被量化。看摘要描述里强调的三个方向最小化运行成本、最大化可再生能源利用率、保证供电可靠性。落到 MILP 模型里可再生能源利用率通常通过「弃光弃风惩罚」来间接最大化供电可靠性通过「负荷缺电惩罚」来保证这两个惩罚项都会折进目标函数。常见的目标函数由四块组成柴油机组的燃料成本通常用二次函数 $C(P)aP^2bPc$ 描述但在 MILP 框架里要做分段线性近似储能充放电损耗成本一般折算成对循环寿命的损耗或单位充放电量费用从主网购电的成本按分时电价计算弃风弃光惩罚项和失负荷惩罚项用来保证可再生能源优先消纳和可靠性。YALMIP 里目标函数用Objective Objective ...逐项相加最后传给optimize。一个典型的成本累加写法是Objective 0; % 机组燃料成本假设用凸分段线性函数近似这里用一次项简化示意 for t 1:T Objective Objective sum(a_gen .* P_gen(:,t) b_gen .* on_gen(:,t)); % a_gen 是边际成本系数b_gen 是空载成本on_gen 是机组启停状态 0/1 end注意这里的on_gen必须是binvar定义的二进制变量它跟P_gen相乘出现了非线性。如果直接写on_gen .* P_gen问题就不是 MILP 了。工程上的处理方式是避免这种乘积用约束把二者关联起来。3.2 储能建模状态变量、功率耦合与逻辑约束储能是这份资源里最核心的模块。一个完整的储能模型需要三组变量每个时段的荷电状态 SOC、充电功率、放电功率。SOC 是连续变量充放电状态是 0-1 变量并且充放电不能同时进行。SOC 的递推公式写成功式约束是Constraints [Constraints, SOC(t1) SOC(t) ... (P_ch(t) * eta_ch - P_dis(t) / eta_dis) * dt / Cap]; % eta_ch 充电效率、eta_dis 放电效率、Cap 储能容量、dt 时段长度(小时)这条约束是储能建模的灵魂。注意充电时能量乘以效率存入放电时按效率折算消耗不对称处理能让 SOC 的变化更接近真实电池特性。充放电不能同时发生的约束有两种写法。一种是用两个二进制变量加互斥约束% 用 binvar 定义充放电状态 C_ch binvar(1, T); C_dis binvar(1, T); Constraints [Constraints, C_ch C_dis 1]; % 表示同一时段不能既充电又放电 Constraints [Constraints, 0 P_ch P_ch_max .* C_ch]; Constraints [Constraints, 0 P_dis P_dis_max .* C_dis];另一种是直接限制充放电功率与 SOC 不对应但会破坏线性性不推荐。上面这种互斥约束在 CPLEX 里处理得非常快两个二进制变量加一条线性约束的问题规模可以忽略不计。3.3 三类容易写错的约束功率平衡是每条代码里最先检查的约束。微电网的功率平衡不是一根母线那么简单需要把分布式电源、储能、负荷、主网交互全部纳入等式Constraints [Constraints, P_gen_total(t) P_wt(t) P_pv(t) ... P_dis(t) P_grid_buy(t) P_load(t) P_ch(t) P_grid_sell(t)];这里P_grid_buy和P_grid_sell必须也用二进制变量做互斥否则 CPLEX 会算出一个同时买入和卖出的荒谬解。主网交互存在分时电价利用这个等式和互斥约束模型会自动决定什么时候从电网买电、什么时候反送。机组出力约束不只是上下限的问题。如果机组可以停机那下限就不是 0而是要乘上启停变量P_min * on P P_max * on。这样的约束保证停机时机组出力强制为 0避免出现「机组关了但还在发电」的数学上成立、物理上荒谬的解。另外还有爬坡约束限制机组从一个时段到下一个时段的出力变化Constraints [Constraints, P_gen(:,t1) - P_gen(:,t) ramp_up]; Constraints [Constraints, P_gen(:,t) - P_gen(:,t1) ramp_down];这几条约束是调度模型里最容易被忽略但工程价值最高的部分。忽略爬坡约束求解结果会给出一台机组从 0 到额定功率的瞬间跳变这在真实电厂里是不可能发生的。4. microgrid_Cplex.m 代码拆解从参数区到结果输出的完整流程4.1 代码结构总览这个文件是整套资源的入口也是唯一的核心文件。它大致分成四块数据定义、变量声明、约束组装、求解与结果输出。理解它的组织结构比一行行读代码更高效。%% 第一步定义基础数据 % 当前代码里用的是 24 个时段的单日调度时段长度 dt1 小时 T 24; dt 1; % 负荷功率kW代码里以列向量的形式给出 P_load [40 42 45 44 43 ...]; % 按实际代码里的数值走 % 风机和光伏出力预测曲线 P_wt [25 25 20 15 ...]; P_pv [0 0 0 0 ...];这段定义的就是「不可控」的部分。负荷是必须满足的风机和光伏出力是预测出来的本质上就是给定的输入序列。你要替换自己的场景改这里的数据就行。发电机参数和储能参数也在这一块。发电机需要定义最大最小出力、燃料成本系数、爬坡速率储能需要定义容量、最大充放电功率、充放电效率、SOC 的上下限和初值。这些参数如果跟代码里给定的场景不同只需改数字不需改代码结构。4.2 变量声明哪几个是连续、哪几个是 0-1变量声明是 YALMIP 建模最容易出错的地方。模型里变量类型声明错了求解速度会差一个数量级甚至直接不可解。% 连续变量机组出力、储能充放电功率、SOC、主网交互 P_gen sdpvar(n_gen, T); % 每台机组每个时段的出力 (kW) P_ch sdpvar(1, T); % 储能充电功率 (kW) P_dis sdpvar(1, T); % 储能放电功率 (kW) SOC sdpvar(1, T1); % 荷电状态注意是 T1 个时段首尾衔接 P_grid_buy sdpvar(1, T); % 从主网购电 P_grid_sell sdpvar(1, T); % 向主网售电 % 0-1 变量机组启停、储能充放互斥、购售电互斥 on_gen binvar(n_gen, T); % 机组启停状态 1开机 C_ch binvar(1, T); % 储能充电状态 C_dis binvar(1, T); % 储能放电状态 C_buy binvar(1, T); % 购电状态 C_sell binvar(1, T); % 售电状态这里有几个细节值得注意。SOC声明成T1是为了让约束式SOC(t1) SOC(t) ...在tT时不越界。这是写代码时很容易出现 index 越界的地方一旦 SOC 声明成T而递推式写到SOC(T1)Matlab 直接报错血泪经验。on_gen是n_gen × T的矩阵也就是每台机组每个时段都要有一个独立启停变量。如果你有 3 台机组跑 24 小时那就是 72 个二进制变量这还只是机组的。加上储能 48 个和购售电 48 个总共有 168 个 0-1 变量。对 CPLEX 来说这个规模是「零热身」几十毫秒就能证明最优。4.3 约束组装顺序不重要但每条都必须是线性表达式约束在 YALMIP 里可以用中括号数组一行一行拼也可以一段一段追加。组装顺序和解算效率无关CPLEX 拿到的是完整的约束集合不是按顺序处理的。Constraints []; % 功率平衡约束 for t 1:T Constraints [Constraints, ... sum(P_gen(:,t)) P_wt(t) P_pv(t) P_dis(t) P_grid_buy(t) ... P_load(t) P_ch(t) P_grid_sell(t)]; end % 储能 SOC 递推约束 for t 1:T Constraints [Constraints, ... SOC(t1) SOC(t) (P_ch(t) * eta_ch - P_dis(t) / eta_dis) * dt / Cap]; end功率平衡这里我建议把储能充电功率放在等式右侧当成负荷放电放在左侧当成电源物理含义上更顺。有的代码把充放电都放左侧写成P_ch - P_dis的形式也能跑但可读性差排错时容易看走眼。机组和储能的上下限约束也需要在组装区完成。上下限的写法要特别注意把二进制变量乘进去这一条忘了写CPLEX 会自己发挥出「机组半开」的解。4.4 求解与后处理检查最优性标记比看数值更重要求解只有一行result optimize(Constraints, Objective, options);但这一行之后的工作是判断result.problem返回码if result.problem 0 disp(最优解找到); P_gen_opt value(P_gen); % 取出机组出力 SOC_opt value(SOC); % 取出 SOC 曲线 else disp([求解失败: yalmiperror(result.problem)]); endyalmiperror函数会把错误码翻译成人能看懂的话比如Infeasible problem表示约束冲突、Solver output error表示 CPLEX 崩了等等。这是排错的第一步远比你自己猜原因高效。出结果后我一般会画三张图各机组出力堆叠图、储能充放电功率与 SOC 曲线、购售电功率曲线。画图不是形式主义是检查约束有没有生效的最直观方式。比如 SOC 曲线如果出现跳变那 SOC 递推公式十有八九写错了购售电曲线如果同时出现非零那购售电互斥约束肯定没写进去。5. YALMIP 与 CPLEX 配置实战环境搭建、求解报错与四个高频坑5.1 环境配置确认三件事Matlab 里装 YALMIP 很简单把 YALMIP 文件夹加到路径即可。但 CPLEX 的配置有三个坑每个都能让你solver not found安装位数必须一致。Matlab 是 64 位就装 64 位 CPLEX32 位对 32 位。混搭时 YALMIP 会提示找不到求解器。路径要加到 Matlab 里。CPLEX 安装完成后需要把cplex/matlab文件夹添加进 Matlab 路径。YALMIP 通过sdpaisavailable(cplex)检查能否找到 CPLEX。Java 和编译器版本兼容。新版 CPLEX12.10 以上对高版本 Matlab 支持较好但老版本的 CPLEX 配新 Matlab 会出莫名其妙的内存错误。验证配置是否成功一行命令的事yalmiptest(cplex)提示系统返回Successfully solved表示配置正常。如果返回Failed按上面三个顺序排查。5.2 求解失败先看返回码再改模型optimize()返回结构化错误时最常见的两类是Infeasible和Numerical problem。Infeasible意思是约束之间打架可行域为空。出现这个现象先检查 SOC 递推约束储能初值和末值设置不合理是最常见的元凶。比如要求SOC(T1) SOC(1)即储能必须回到初始电量但容量太小或负荷太高根本没有可行域那就只能放宽初末 SOC 相等约束或者增大储能容量。Numerical problem通常意味着模型里有数量级差异巨大的系数。比如功率是以 kW 为单位、SOC 是 0 到 1 的标幺值、成本系数是几百块钱每千瓦时这些数字放在一起CPLEX 的数值求解过程容易出问题。解决方法是统一量纲把功率除以基准值比如 100 kW让所有系数落在相近的数量级上。5.3 四个高频坑按踩坑频率排序坑一储能充放电同时非零。现象SOC 曲线平稳但充电功率和放电功率在同一个时段都大于 0。原因忘了加C_ch C_dis 1这条互斥约束。解决二进制互斥加上问题立刻消失。Constraints [Constraints, C_ch(t) C_dis(t) 1];坑二购电和售电同时进行。现象目标函数里出现「高价买入、低价卖出」的套利假象总成本算出来为负。原因这个现象其实是主网交互约束写漏了CPLEX 认为两个方向都是变量就能同时交易。解决跟坑一同一套路加二进制互斥。Constraints [Constraints, C_buy(t) C_sell(t) 1];坑三机组出力下界归零不彻底。现象机组on_gen为 0 的时段P_gen仍然有非零输出。原因上下限约束写成P_min P_gen P_max忘记乘上启停变量导致 CPLEX 认为停机也能发最小出力。解决Constraints [Constraints, P_gen(:,t) P_max .* on_gen(:,t)]; Constraints [Constraints, P_gen(:,t) P_min .* on_gen(:,t)];坑四SOC 出现越界。现象SOC 算出来超过 100% 或低于 0但没报错。原因丢掉了 SOC 上下限约束。这个约束在 YALMIP 里写起来不费力但在多种约束混合时容易被淹没忽略。Constraints [Constraints, SOC_min SOC(1:T1) SOC_max];5.4 求解时间不可控怎么办24 时段 × 几台机组的问题CPLEX 都是秒解。如果求解时间异常长问题往往不在 CPLEX而在模型本身的「病态」——比如约束里有大 M 值、或者二进制变量之间存在冗余对称性。先看约束矩阵是不是有数量级差异巨大的数据再看二进制变量是不是存在对称性——比如两台发电参数完全一样的机组交换启停状态后目标函数一样CPLEX 会浪费时间证明「哪个解更优」。解法是打破对称给两台同型号机组设略微不同的成本系数哪怕只差 0.01求解速度都会明显改善。这门玄学处理方式在工程里非常实用。6. 把单日调度改成多日滚动模型验证与扩展的两条实用路径拿到这套资源的第一步建议是先跑通原模型用已有的 24 小时结果验证自己的环境没问题。然后再做修改。验证的方法很简单看三样东西能不能对上——机组总出力加储能净放电加主网净购电等于负荷SOC 首末状态满足设定目标函数值与手算成本核对量级。三条都通过说明代码完全正常。接着再做修改。我最常改的是把单日调度扩展成多日滚动调度。单日调度的一个弱点是它假设第 1 个时段前 SOC 已知第 24 个时段后 SOC 强制回到初值这在真实场景里意味着每天结束储能必须清空。这个假设显得刻意多日滚动调度能把第一天到第 N 天完整串起来。具体做法是把单位从「天」换成「调度周期」把所有数组的维度从 T24 扩展为 TN×24SOC 的初值只在第一天设定中间不强制归位只在最终时段约束 SOC 范围。改起来工作量不大% 多日模型只需把 T 改成 N*24 T 7 * 24; % 7 天滚动 % SOC 初值只在起点设定, 去掉 SOC(T1) SOC(1) 的强制回位 Constraints [Constraints, SOC(1) SOC_init]; % 最终 SOC 范围放宽: 允许在一定区间内 Constraints [Constraints, SOC(T1) SOC_min_final];这样扩展后有一个实际价值——晚上低谷电价时段储能充电白天高峰放电跨天套利空间会被 CPLEX 自动利用。单日调度做不到这一点因为最后一天必须清空 SOC 导致最后几个时段的行为被扭曲。另一个实用改进是给目标函数加弃风弃光惩罚。很多微电网场景里分布式能源渗透率高调度结果会优先压低柴油机出力来消纳光伏和风电但如果光伏和风电加起来超过负荷加储能充电能力就得弃掉。目标函数里加一个正比于弃电量的惩罚项求解器会在「弃电惩罚」和「柴油机降出力成本」之间做权衡结果更贴近真实运行逻辑。% 弃风光量等于预测出力减实际消纳量 P_curtail_wt P_wt - P_wt_used; P_curtail_pv P_pv - P_pv_used; % 目标函数里加惩罚项, 惩罚系数大于柴油机边际成本 Objective Objective 1.5 * sum(P_curtail_wt P_curtail_pv);做完这两步这套资源就不再是「能跑的示例代码」而是真正能应对多日连续调度和新能源高渗透场景的工具。从那以后我每次拿到一个新的调度工程都会先不急着改代码而是把 SOC 递推、互斥约束、爬坡约束三块单独拎出来逐条核对——这三块一旦写错求解器给的不是最优解是物理上根本不可能发生的解。这份资源把这套框架都写好了你再改参数和扩展场景时的返工率会低很多。希望帮到你。本文还有配套的精品资源点击获取