基于Matlab+Yalmip的微电网日前经济调度建模与实现
发布时间:2026/9/30 9:01:11来源:尧图网络
你先别急着打开 Matlab 写代码。微电网日前经济调度这个题目在电气工程、储能和智能电网方向的课设、毕设里出现频率非常高但真正动手用 Matlab 实现的时候绝大多数人卡住的不是公式推导而是“变量怎么定义、约束怎么摆、求解器为什么报无解”。基于风光储能和需求响应的日前经济调度本质上是把“明天 24 小时怎么安排设备出力”变成一个最优化问题给定光伏、风电、负荷预测曲线和分时电价让储能低充高放、让柔性负荷避开高峰、让微电网和主网的购售电策略最省钱同时不违反设备运行限制。这篇文章就按“建模思路 - 数学式子 - Yalmip 代码 - 避坑经验”的顺序把整套流程完整走一遍适合正在做课设的研究生、刚接触微电网仿真的工程师以及想搞清楚需求响应怎么落到代码里的人。1. 先把优化目标拆开你究竟在调什么1.1 一块钱也要算清楚日前经济调度到底在算哪笔账经济调度的“经济”两个字不能笼统理解成“成本最小”。它在数学上是一个非常具体的优化目标未来 24 小时微电网从主网购电要花钱往主网卖电有收入储能充放电有效率损耗需求响应要付给用户补偿弃风弃光还会造成清洁能源浪费。把这些全部折算成货币加在一起才是“一天的总运行成本”。为什么强调“日前”因为光伏、风电和负荷预测在提前一天的时间尺度上相对可靠分时电价也在日前就公布这时候做出来的调度计划有实际指导意义。到了当天风电光伏可能又有波动那是日内滚动优化和实时控制该处理的事。日前调度相当于排一个主计划后续实时调度只需要在它附近做小幅度修正。我用一个生活化的类比家里一天的开销收入是固定的电费峰谷价差很大有些电器可以等晚上再用。你自然会避开峰值电价开洗衣机但前提是你知道明天几点最贵、几点风最大。日前经济调度就是这个“家庭用电计划”只不过比家庭场景多了储能、弃风弃光惩罚、需求响应补偿这些约束。1.2 风光储和需求响应在模型里的身份完全不同风电和光伏在模型里是“不可控电源”。它们边际成本为零有出力就用但可能出现大发时段负荷用不完需要弃风弃光。弃风弃光不能白弃如果在目标函数里不加惩罚优化器为了省钱可能故意弃掉大量清洁能源。所以一般会给弃风弃光加一个惩罚系数这个系数不用特别大只要能让优化器在“弃电”和“买高价电”之间做出合理取舍就行。储能是“可控调节器”。它的作用不是发电而是转移电量低谷电价时充电高峰电价时放电同时在光伏大发时吸收多余电量。但储能不是免费的除了设备投资折旧还有充放电效率带来的能量损失。这部分损失不需要直接写进目标函数它会通过 SOC 状态方程间接体现充进去 1 MWh取出来可能只有 0.95 MWh这个差值就是成本。需求响应是“负荷侧主动管理”。我们平时说的需求响应大致分两类价格型需求响应和激励型需求响应。价格型是根据分时电价用户自发调整用电时间激励型是微电网运营商和用户签协议高峰时段削减或转移负荷运营商给补偿。在日前调度的代码里最容易落地的是激励型里的可削减负荷和可转移负荷。可削减负荷就是直接砍掉一部分用电可转移负荷是那一批用电量必须守恒只是换个时间用。这三者的约束性质也完全不同风光出力受预测值约束储能受 SOC 上下限和充放电功率约束需求响应受用户可调容量约束。想用一个求解器把这三类变量一次性算出来就得把所有物理规则写成数学式子这就是第 2 节要做的事。2. 数学建模怎么把物理规则写成约束2.1 目标函数一天的运行费用由哪几项组成先给结论日前经济调度的目标函数通常写成下面这种形式。min sum_{t1}^{24} [ c_buy(t) * Pbuy(t) * dt - c_sell(t) * Psell(t) * dt c_cut * DR_cut(t) * dt c_trans * (DR_in(t) DR_out(t)) * dt c_abandon * (Pcur_w(t) Pcur_pv(t)) * dt ]逐项解释一下。第一项是从主网购电的费用(c_buy(t)) 是 t 时段购电价(Pbuy(t)) 是购电功率乘上时间步长 (dt) 就是电量再乘价格就是钱。第二项是售电收入前面是减号因为卖电给主网能赚钱会降低总运行成本。第三项是需求响应补偿用户让你削减了 (DR_cut(t)) 这么多负荷你不能白让人家配合。第四项是可转移负荷的调度成本我用 (DR_in) 和 (DR_out) 分别表示转入和转出的功率两项都产生补偿或管理成本。第五项是弃风弃光惩罚(Pcur_w) 是弃风功率(Pcur_pv) 是弃光功率。这里有个重要的建模习惯目标函数里的所有项最好是线性的。为什么不建议加“储能单次充放电折旧成本”这类非线性项因为线性项能让整个问题变成混合整数线性规划也就是 MILP求解速度快、全局最优性有保证。如果你用“电池循环次数折算成本”这种函数很容易引入非线性甚至非凸项问题就从 MILP 变成 MINLP求解难度直接上一个台阶。实际工程中储能寿命损耗可以近似成固定折旧成本加进目标或者干脆不写通过 SOC 状态方程里的效率系数体现。2.2 功率平衡、储能约束和弃风弃光约束功率平衡是调度的硬性约束也是整个模型里最重要的等式。单母线模型下每一时刻的功率必须守恒Ppv(t) - Pcur_pv(t) Pwt(t) - Pcur_w(t) Pdis(t) Pbuy(t) Pload(t) - DR_cut(t) - DR_out(t) DR_in(t) Pch(t) Psell(t)左边是供给光伏实际出力、风电实际出力、储能放电、主网购电。右边是需求原始负荷经过削减和转移之后的净负荷加上储能充电和向主网售电。注意我把弃风弃光放到了左边用“预测出力减去弃电量”表示实际出力所以弃电量天然被限制在 0 到预测出力之间。储能部分需要写三类约束。第一类是 SOC 状态转移这是整个模型里最容易出错的式子SOC(t1) SOC(t) eta_ch * Pch(t) * dt - Pdis(t) / eta_dis * dt(SOC) 是储能电量单位 MWh(eta_ch) 是充电效率(eta_dis) 是放电效率。我习惯把 SOC 变量定义成 (T1) 个从 (SOC(1)) 到 (SOC(T1))这样第 t 个小时结束后的电量就是 (SOC(t1))写起来不会索引错乱。第二类是 SOC 上下限约束防止过充过放。第三类是充放电功率上限以及互斥约束0 Pch(t) Pch_max * u_ch(t) 0 Pdis(t) Pdis_max * u_dis(t) u_ch(t) u_dis(t) 1 u_ch(t), u_dis(t) 是 0/1 变量为什么要加互斥约束因为如果不禁止同一时段既充电又放电优化器可能玩出“左手倒右手”的花招用很低的价格买电充电又用更高的价格放电卖给主网或者用购电充电再放电给本地负荷本质上是在套利而不是调度。现实中一台储能设备同一时刻不能既充又放所以必须用两个 0/1 变量把它锁死。弃风弃光约束就比较简单了0 Pcur_w(t) Pwt(t) 0 Pcur_pv(t) Ppv(t)这表示弃电量不能为负也不能超过当前时段的预测出力。加上目标函数里的惩罚系数优化器才会在必要时刻才选择弃电。2.3 需求响应约束不能为了省钱把用户全砍光需求响应这块很多初学版本只写一个“负荷削减量小于上限”就完事了实际是不够的。我把常见做法拆成可削减和可转移两部分。可削减负荷的约束是0 DR_cut(t) alpha_cut * Pload(t)其中 (alpha_cut) 是最大削减比例一般取 5% 到 15%。如果你不加上限目标函数里的购电价很高时优化器恨不得把负荷全部削掉最后结果就是负荷曲线被砍成一条直线完全没有实用性。补偿单价 (c_cut) 也要设置得比购电价差略低一些否则会出现“用户配合削负荷”反而比“高价买电”更贵的奇怪情况。可转移负荷的逻辑更讲究。它描述的是这样一种场景某个工业流程、洗衣机或者充电桩今天必须用掉固定的电量但具体在哪个时段用可以灵活安排。模型里我用 (DR_in(t)) 表示 t 时段从其他时段转入的电量用 (DR_out(t)) 表示 t 时段转出去的电量。约束有三条0 DR_in(t) alpha_shift * Pload(t) 0 DR_out(t) alpha_shift * Pload(t) sum(DR_in) sum(DR_out)最后一条是电量守恒转出去多少必须从其他时段转回来多少否则就不是“转移”而是凭空削减了。实际代码里常用“净转入变量” (DR_shift(t)) 来表示范围可正可负这样功率平衡等式更简洁但目标函数的绝对值处理会比较麻烦。我建议用 (DR_in) 和 (DR_out) 两个非负变量分开写虽然变量多一点但目标函数不需要用 abs 函数求解更稳定。2.4 购售电互斥与主网交换功率约束微电网和主网通过公共连接点交换功率。在做日前调度时一般不允许同一时段既购电又售电原因有两个一是技术上公共连接点通常只有一个计量方向二是经济上如果购电价低于售电价模型会自动套利——低价买进来高价卖出去平白无故赚差价。所以在约束里加一个 (u_grid(t)) 二进制变量0 Pbuy(t) Pgrid_max * u_grid(t) 0 Psell(t) Pgrid_max * (1 - u_grid(t))这样每个时段要么购电要么售电要么两者都不做。(Pgrid_max) 是 PCC 允许的最大交换功率取决于变压器容量和并网协议。很多教程里省略这个约束认为购电价天然高于售电价不会出现同时买卖。这确实在大多数电价结构下成立但你在调试时如果发现结果里出现同时购售或者电价数据做过特殊设置就必须把这个二进制变量加回来。3. Matlab 实操Yalmip 建模与求解完整流程3.1 数据准备24 小时风光负荷曲线和电价先说明一下技术选型。模型是 MILP所以我用 Matlab 加 Yalmip 工具包求解器用 Gurobi。Yalmip 的好处是语法接近数学表达式约束写起来直观而且可以随时切换求解器。你如果没有 Gurobi 授权用免费的 CBC 也可以解小规模 MILPYalmip 会自动识别。数据准备阶段我建议把 T、dt、预测曲线都放在脚本最前面方便改参数。下面是一份可以直接跑通的数据示例T 24; % 日前调度时段数 dt 1; % 时间步长单位小时 % 预测出力单位 MW Ppv [0 0 0 0 0 0.4 1.0 2.0 3.0 3.6 3.8 3.5 ... 3.0 2.2 1.4 0.8 0.3 0 0 0 0 0 0 0]; Pwt [2.0 1.8 1.6 1.5 1.3 1.2 1.1 1.2 1.3 1.5 ... 1.7 1.6 1.5 1.4 1.3 1.1 1.0 0.9 0.8 0.7 ... 0.9 1.1 1.3 1.5]; Pload [6.0 5.8 5.5 5.2 5.0 5.2 5.8 6.5 7.2 7.8 ... 8.2 8.4 8.0 7.6 7.2 7.0 7.2 7.8 8.4 9.0 ... 9.5 9.8 9.2 8.0]; % 分时电价单位元/MWh price_buy [0.38 0.38 0.38 0.38 0.38 0.45 0.60 0.80 ... 0.90 0.95 1.00 1.00 0.85 0.70 0.65 0.60 ... 0.70 0.85 0.95 1.00 1.00 0.90 0.70 0.50] * 1000; price_sell price_buy * 0.8;这里有一个非常容易踩坑的单位问题功率单位是 MW时间步长 (dt) 是小时所以“功率乘时间”得到的能量单位是 MWh。如果你设置 (dt1)那 (Pch(1) * dt) 刚好是 1 MWh。但如果你改成 15 分钟一个时段也就是 (dt0.25)那充电功率 1 MW 充 15 分钟实际充进去的能量是 0.25 MWh。所有涉及能量的地方包括 SOC 方程和购售电费用都必须乘 (dt)否则一天下来账单会差好几倍。3.2 Yalmip 变量定义与约束组装变量定义直接对应第 2 节的数学模型。我把所有决策变量列出来然后一组一组加约束。% 决策变量 Pbuy sdpvar(T,1); % 购电功率 Psell sdpvar(T,1); % 售电功率 Pch sdpvar(T,1); % 储能充电功率 Pdis sdpvar(T,1); % 储能放电功率 SOC sdpvar(T1,1); % 储能电量状态SOC(1)为初始值 Pcur_w sdpvar(T,1); % 弃风功率 Pcur_pv sdpvar(T,1); % 弃光功率 DR_cut sdpvar(T,1); % 可削减负荷 DR_in sdpvar(T,1); % 可转移负荷转入 DR_out sdpvar(T,1); % 可转移负荷转出 % 二进制变量 u_ch binvar(T,1); % 充电状态 u_dis binvar(T,1); % 放电状态 u_grid binvar(T,1); % 购售电状态1为购电0为售电 % 储能和主网参数 SOC_init 2; % 初始电量 MWh SOC_min 0.5; % 最低电量 MWh SOC_max 4; % 最高电量 MWh Pch_max 2; % 最大充电功率 MW Pdis_max 2; % 最大放电功率 MW Pgrid_max 4; % 与主网最大交换功率 MW eta_ch 0.95; % 充电效率 eta_dis 0.95; % 放电效率 % 成本参数 c_cut 100; % 可削减负荷补偿元/MWh c_trans 80; % 可转移负荷补偿元/MWh c_abandon 200; % 弃风弃光惩罚元/MWh接下来是约束集合。在 Yalmip 里用一条一条等式和不等式往前拼接 (C) 这个 cell最后喂给 optimize 函数。C []; % 主网购售电约束 C [C, 0 Pbuy Pgrid_max*u_grid]; C [C, 0 Psell Pgrid_max*(1-u_grid)]; % 储能功率约束 C [C, 0 Pch Pch_max*u_ch]; C [C, 0 Pdis Pdis_max*u_dis]; C [C, u_ch u_dis 1]; % SOC 状态转移和上下限 C [C, SOC SOC_min, SOC SOC_max]; C [C, SOC(1) SOC_init]; C [C, SOC(2:T1) SOC(1:T) eta_ch*Pch*dt - Pdis/eta_dis*dt]; % 弃风弃光约束 C [C, 0 Pcur_w Pwt]; C [C, 0 Pcur_pv Ppv]; % 需求响应约束 C [C, 0 DR_cut 0.10*Pload]; C [C, 0 DR_in 0.10*Pload]; C [C, 0 DR_out 0.10*Pload]; C [C, sum(DR_in) sum(DR_out)]; % 功率平衡约束 C [C, Ppv - Pcur_pv Pwt - Pcur_w Pdis Pbuy ... Pload - DR_cut - DR_out DR_in Pch Psell];我特意把购售电互斥约束写在最前面防止你在后面加其他约束时忘了。功率平衡等式里的 DR_out 是减号表示负荷被转移走了DR_in 是加号表示负荷从别的时段转进来了。如果你把符号搞反结果里会出现“越转移负荷越高”的荒谬现象。目标函数按第 2 节的五项费用组合objective sum( (price_buy.*Pbuy - price_sell.*Psell)*dt ... c_cut*DR_cut*dt ... c_trans*(DR_in DR_out)*dt ... c_abandon*(Pcur_w Pcur_pv)*dt ); % 求解 ops sdpsettings(solver, gurobi, verbose, 1); ops.gurobi.TimeLimit 120; diagnosis optimize(C, objective, ops);如果 (diagnosis.problem) 返回值是 0说明求解成功如果是 1说明无界或求解器内部错误如果是 15说明模型不可行。Gurobi 的 TimeLimit 设置为 120 秒防止极端情况下卡住不返回。3.3 结果提取、绘图和成本统计求解完成后用 value 函数提取结果。我习惯一次性把关键变量都取出来放到一个结构体里方便后面画图和写报告。Pbuy_opt value(Pbuy); Psell_opt value(Psell); Pch_opt value(Pch); Pdis_opt value(Pdis); SOC_opt value(SOC(2:T1)); DR_cut_opt value(DR_cut); DR_in_opt value(DR_in); DR_out_opt value(DR_out); total_cost value(objective);画图时我至少会画两张图。第一张是功率平衡堆叠图把实际光伏出力、实际风电出力、储能放电、购电功率按正方向堆起来看它和净负荷是否平衡。第二张是 SOC 曲线叠加电价曲线通常能看到储能聪明地在低谷前充电、在高峰前放电。这样做的好处是调试时有直观画面如果 SOC 曲线在没有任何约束问题的情况下会长得乱七八糟基本能确定数据或逻辑有问题。这里还值得做一个对比实验跑一次“不含需求响应和储能”的纯购电模型再跑一次完整模型算一下总成本差多少。这是论文里最有说服力的一段结果也是你用来验证模型正确性的手段。如果加入储能和需求响应之后总成本反而更高先别急着改目标函数重点查储能效率、补偿单价和约束范围是不是设置得离谱。4. 实战避坑无解、套利和 DR 滥用怎么处理4.1 模型无解先别怀疑求解器我用 Yalmip 调微电网模型这几年遇到的无解问题九成不是求解器的锅而是约束本身矛盾。最快的定位方法是求解之后马上执行check(C)Yalmip 会逐条返回每个约束的残差。如果某条约束显示残差很大说明它所在的那一组约束很可能就是冲突源头。常见矛盾有下面几种。第一种是单位混用。SOC 变量用的是 MWh但你在功率平衡等式里直接拿 MW 和 MWh 相加这从量纲上就不成立优化器当然找不到可行解。第二种是储能初始和末端状态锁死导致无解。比如 SOC(1) 要求等于 2SOC(T1) 也强制等于 2但全天净负荷和电价结构让储能不得不在末端放光才能满足功率平衡这时候终端等式就会把所有可行解全排掉。处理办法是把终端等式放宽成软约束或者设置一个允许的小偏差C [C, abs(SOC(T1) - SOC_init) 0.1];但注意不要随便加 absYalmip 处理绝对值会引入辅助变量最好展开写成C [C, SOC(T1) SOC_init - 0.1]; C [C, SOC(T1) SOC_init 0.1];第三种是需求响应约束把负荷压得过低。如果 alpha_cut 和 alpha_shift 设得太大而本地光伏风电出力又不足功率平衡等式可能要求购电功率超过 Pgrid_max于是无解。我给的示例用 10% 是经验值实际应根据项目要求调整。4.2 储能同时充放和 SOC 曲线异常不加互斥约束时优化器极有可能在同一时段既充电又放电。举一个具体例子假设某个时段购电价很低模型可以买电充电同时让储能放电给负荷只要放电收益大于购电成本这种“充放套利”就会重复出现。表面上看功率平衡等式依然成立但物理上储能设备完全是在空转浪费效率。所以 (u_ch u_dis \leq 1) 这行约束绝对不能省。SOC 曲线异常还有一种常见表现全天保持在上限或下限不动。这不一定是模型错了很可能是储能容量配置过大或者峰谷价差不足以覆盖效率损耗。我见过一个案例峰谷价差只有 0.1 元/kWh储能往返效率 0.9算下来充放一次能量损耗接近 10%根本不划算储能当然不动作。这时候不要调代码而是要重新审视参数。如果你确实希望储能参与调度就把效率调到 0.95或者放大峰谷价差重新测试。4.3 DR 补偿单价设置不当所有负荷都被削没了需求响应模型的参数是“最像技术问题”的环节——你每改一个数字调度结果可能会从“合理削峰”变成“灾难性削负荷”。如果 (c_cut) 设置得太低比如只有 10 元/MWh而峰值购电价是 1000 元/MWh优化器会选择把高峰时段所有可削减负荷全部削掉因为削掉 1 MWh 能省 1000 元的购电费只付 10 元补偿这买卖太划算了。但真实用户不可能让你无限削负荷。解决思路有三个一是把 alpha_cut 上限设小比如 5%二是提高补偿单价让 DR 在经济上没有吸引力到滥用三是加一个最低负荷约束保证本地生产生活用电底线。工程上常用第一种和第二种组合。如果你想写得更细可以把 DR 补偿设置为阶梯价格比如前 5% 按 100 元/MWh 补偿超过 5% 的部分按 200 元/MWh 补偿这样模型会自然控制削减量。阶梯价格是分段线性函数Yalmip 里可以用 sos2 约束实现但小规模模型直接用固定上限更省事。4.4 求解时间过长和数值稳定性对于 24 时段的日前调度二进制变量其实很少储能两个变量乘 24 小时购售电状态 24 个加起来 72 个Gurobi 一般一两秒就能搞定。如果你发现求解时间异常长先看看模型里是不是引入了非线性项。最常见的是在目标函数里写了 (Pbuy.*Pbuy) 这种二次项或者在约束里写了 (Pch.*Pdis) 这种两个变量相乘。这些都会让问题从 MILP 变成 MIQP 甚至 MINLP求解难度完全不一样。如果确实需要保留非线性我建议手动检查每一项能否线性化。比如需求响应补偿成本如果按电量分段可以用 SOS2储能寿命损耗如果按充放电深度算可以用若干分段线性近似。这些都是成熟方案但没必要在一开始就上。先把线性模型跑通再逐步加入精细化约束这是最稳妥的调试路径。数值稳定性方面要注意大数和小数混用。电价单位如果是元/MWh数值在 300 到 1000弃风弃光惩罚如果写成 200和购电价同一量级没有大问题。但如果你把 SOC 容量设置成 4000 kWh又用了 MWh 作单位SOC 约束里会出现 4000 和 0.95 相乘数值范围跨度太大求解器容易计算残留误差。我习惯把所有单位统一成 MW、MWh、元/MWh 一套体系SOC 不要写成 kWh省掉没必要的换算出错。4.5 一份能直接跑通的最小代码结构说了这么多最后给你整理一个最小可运行的代码骨架。它不包含任何炫技功能但能最直观地展示 Yalmip 建模逻辑。完整代码文件里应该包含四部分第一段是数据准备第二段是变量定义第三段是约束和优化目标第四段是结果输出。每次修改需求响应比例、储能容量或电价曲线时只需要动第一段和参数区其他代码基本不用动。我在实际项目中养成了一个习惯把“是否启用需求响应”做成一个开关变量。比如设置一个DR_enable 1为 1 时加入需求响应约束和补偿成本为 0 时直接跳过这些式子。这样做对比实验非常方便论文里需要“有无需求响应”两个场景时不用删代码只要改一个开关。储能也可以用类似的开关控制。这段代码跑出来的结果建议你仔细检查三个数字一是总购电费用二是 DR 补偿总成本三是弃风弃光惩罚总成本。如果三个数字里有一个明显异常比如 DR 补偿成本占了总成本一半那模型可能正在做不合理的调度决策。这时候回到第 4.3 节的方法去调参数比盯着求解器日志有用得多。我自己跑这类模型的体会是微电网日前经济调度的难点从来不是“把目标函数写出来”而是“把每一个约束背后的物理意义想清楚”。SOC 方程里的效率系数、需求响应里的补偿单价、购售电互斥的二进制变量任何一个设置失误都会让结果看起来合理但实际上是错的。你如果能把这套线性模型彻底跑通后续再扩展多微电网协同、日内滚动调度、考虑不确定性的鲁棒优化都会轻松很多。先把 24 时段的单母线模型写稳比贪大求全学一堆算法重要得多。
网站建设高端定制企业官网