ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

微电网调度实战:风光储与电网交互的MILP建模与求解

微电网调度实战:风光储与电网交互的MILP建模与求解 简介面向微电网优化调度研究的一站式仿真资源包聚焦风电、光伏、储能与电网的协同交互提供Matlab与Python双语言代码实现并附有可直接复现的运行结果适合电气工程、自动化等方向本硕学生及科研人员用于课程设计、毕业设计或课题预研。资源包共1549个文件大小约25.24MB以Matlab源文件.m为主体辅以Python脚本、MAT数据、结果图像、HTML说明等形成从建模、求解到结果分析的全流程闭环便于分模块检索与二次开发。目前已有633人参与学习下载包内包含完整调度策略实现、仿真结果及配套数据可大幅缩短从理论到仿真的搭建时间。这套资源尤其适合作为微电网能源管理、多能互补与优化算法应用等研究方向的起点。1. 微电网调度到底在算什么风、光、储能与电网交互的一天微电网调度风、光、储能、电网交互翻译成业务语言就是给一套同时接了风机、光伏板、储能电池、又能和外电网双向买卖电的微电网排一张未来 24 小时的功率分配表。这张表要同时回答四个问题风光各发多少、储能什么时候充什么时候放、什么时候买电、什么时候卖电并且让全天运行成本最低。落到数学上最常用的是混合整数线性规划MILP储能充放电互斥靠 0-1 变量其余约束基本线性交给 Gurobi、CBC 这类求解器单日 24 时段的算例通常一秒内出结果。所以难的不是求解而是把四个元件的物理边界翻译成约束并让 Matlab 和 Python 两个版本跑出完全一致的结果。做这件事的人我接触下来主要是三类微电网能量管理方向的研究生、做园区或海岛微电网方案的系统工程师、把调度算法嵌进 EMS 的软件工程师。如果你手头有风、光、储、负荷的数据想复现一套能出调度曲线和成本结果的代码下面按我的实现习惯展开模型怎么建、双语言怎么落、跑完怎么复盘、坑在哪。2. 元件建模先把边界定住风光出力、储能 SOC 与联络线约束调度代码里七成的篇幅都在做同一件事把物理边界翻译成数学约束。风电和光伏给出每个时段可用出力储能给出 SOC 递推和充放电限制电网交互给出联络线功率上限和分时电价。这四个边界定清楚了剩下的交给求解器找最优组合。2.1 风、光出力模型读预测曲线而不是建物理模型调度模型里风电和光伏不需要建涡轮功率曲线或光伏板等效电路那是资源评估阶段的事。日前调度只需要知道每个时段可用出力上限所以最省事的做法是把预测曲线直接读进来一条 24 点或 96 点15 分钟一个点的风电预测功率、一条同样的光伏预测功率作为调度的输入边界。风电的出力白天夜间都有波动大夜间常是高峰光伏严格只在白天有值日出日落附近爬坡下坡。两条曲线单位统一成 kW时间轴对齐到同一套时段下标这是所有后续工作的前提。调度里还必须给弃风弃光留变量。储能容量有限、联络线有上限总会出现消纳不掉的时段这时用Pc_w(t)、Pc_pv(t)两个非负变量表示被放弃的功率取值只能在 0 和可用出力之间。目标函数里弃电惩罚给不给都行不给求解器会自动优先用光风光给的话系数很小0.01 元/kWh作用只是让结果在多个等价解里更贴近「少弃电」的工程直觉。另外强调一句调度只看有功功率无功、电压、潮流那是配网分析和无功优化的事别混进这个模型。2.2 储能模型SOC 递推、充放电互斥与寿命代价储能是微电网调度里唯一有「记忆」的元件模型核心是荷电状态递推SOC(k1) SOC(k) (η·P_ch(k) − P_dis(k)/η)·Δt / E_rated公式里每个量都有现场对应E_rated 是电池系统额定容量按 kWh 数填η 是单程效率磷酸铁锂取 0.95铅酸取 0.85Δt 是调度步长小时制就是 1。SOC 上下限 0.1~0.9 来自 BMS 保护策略怕过充过放PCS 功率上限决定一小时能充进或放出多少600 kWh 配 300 kW 是常见的 0.5C 配置。这几个参数取值说到底由电池选型和变流器规格决定代码里留成变量后面做敏感性分析直接扫描。除了递推还有三个工程上容易漏的约束。一是充放电互斥用二进制变量u_ch(t) u_dis(t) ≤ 1否则求解器可能出现同一时段既充又放、白白损失效率的伪最优。二是日始末 SOC 对齐SOC(1) SOC(T1)让调度方案每天可持续这也是后面看运行结果时 SOC 曲线总是从 0.5 起、0.5 落的原因。三是吞吐寿命代价每吞吐 1 kWh 记 0.2 元损耗成本放进目标函数避免峰谷价差很小时储能频繁启停。损耗系数是经验值论文里常用 0.1~0.3 区间扫描现场按电池循环寿命折算也可以。2.3 电网交互模型联络线功率上限与分时电价微电网与外电网交互建模上就是两个非负变量购电P_buy和售电P_sell再配联络线功率上限。上限由并网变压器容量决定常见做法是购电上限 800 kW、售电上限 500 kW两个值往往不对称。电价是驱动整个调度行为的核心信号国内工程最常见的分时电价结构是低谷 0.35 元/kWh、平段 0.75 元/kWh、高峰 1.2 元/kWh售电上网电价取购电的 60%~80%。这里有一个值得记住的数学事实只要售电价严格低于购电价目标函数是求最小成本求解器天然不会让买电和卖电同时发生所以购售电互斥约束可以省掉。但如果你的项目里两者价格相等甚至倒挂就必须像储能一样加 0-1 变量做互斥。另外真实并网规程往往限制联络线每分钟爬坡率严格建模要加爬坡约束 |P_grid(t1) − P_grid(t)| ≤ ramp_limit很多教材算例把它省了。我的做法是先不加爬坡约束跑通再对比加与不加的成本差判断现场能不能接受这个波动。3. 优化建模与求解器选型为什么 MILP 是默认答案模型边界定了剩下的就是把它写成「目标 约束」的标准形式然后选求解器。单微电网日前调度是典型的小规模 MILP24 时段只有几千个变量和约束求解速度根本不是瓶颈瓶颈在建模规范和调试手段。这一章把标准形式讲清楚再讲求解器和两条语言的分工。3.1 目标函数与约束的标准形式先看功率平衡那一行把第二章的元件模型收拢目标函数是min Σ_t [ price_buy(t)·P_buy(t) − price_sell(t)·P_sell(t) c_bat·(P_ch(t)P_dis(t))·Δt c_curt·(Pc_w(t)Pc_pv(t))·Δt ]第一项是购电成本第二项是卖电收益所以是减号第三项是储能吞吐损耗第四项是弃电惩罚。核心约束是功率平衡等式P_w(t) − Pc_w(t) P_pv(t) − Pc_pv(t) P_dis(t) P_buy(t) P_L(t) P_ch(t) P_sell(t)这行等式是所有微电网调度代码里最该打红色高亮的一行左边是「发电 放电 购电」右边是「负荷 充电 售电」两边每一时段都严格相等。检查代码时先对这行逐时段手算三个点比看任何报错日志都管用。其余约束按元件分组储能组的 SOC 递推、SOC 上下限、充放电上限、互斥二进制电网组的购售电上下限、联络线爬坡可选新能源组的弃电上下限 0 ≤ Pc ≤ P_avail。所有约束对 t 1..24 展开就是一个标准 MILP 实例。3.2 为什么 MILP 是默认答案全局最优与求解速度的平衡有人会问调度问题用动态规划、遗传算法或者粒子群行不行。都行但我不建议作为第一版。原因很简单这个模型的目标函数和约束全部是线性的除了充放电互斥需要 0-1 变量没有任何非线性项MILP 的商用求解器能给出全局最优解而且有 gap 可以量化。启发式算法比如粒子群参数一多就是玄学同样的算例跑十次可能给十个成本说服不了甲方也说服不了导师。MILP 真正不适用的场景只有两类一是目标或约束里引入了非线性比如电池老化模型是非线性的、电价是阶梯状的非凸函数二是要考虑风光不确定性需要两阶段或分布鲁棒模型。这两种情况我的做法是核心 MILP 保留外面套滚动时域或场景采样而不是把求解器换成启发式。单时段决策的可靠性永远是第一位的。3.3 求解器选型Gurobi、CBC 和 OR-Tools 怎么选求解器这条线我按「建模语言 求解后端」拆开说。Matlab 侧最顺手的组合是 YALMIP 建模 Gurobi 求解YALMIP 把 sdpvar、binvar 封装得接近数学表达式写约束不容易错Gurobi 的 MIP 求解速度是商用第一梯队学术授权免费。Python 侧按开箱即用程度排序PuLP CBC 最省事pip 装完就能跑OR-Tools 的 CP-SAT 对整数问题支持好但建模风格偏底层商业求解器 Gurobi 也有 Python 接口性能和 license 要求同 Matlab 侧。对比项Matlab YALMIP GurobiPython PuLP CBC建模上手快接近数学表达式中等对象风格要适应两天求解性能秒级gap 小小规模够用上百节点变慢画图内置 plot导出方便matplotlib也不难部署进产品授权成本高免费易集成适合场景论文、教学、快速验证工程原型、算法迭代、EMS 集成我的环境是 matlab 2026b YALMIPPython 用的 3.9 配 PuLP。YALMIP 装完记得 addpath否则运行直接报「未定义函数或变量 sdpvar」。如果本机没有 matlab license先在 matlab 在线网页版 把算法验证一遍再回到本地出图思路完全一样。3.4 Matlab 与 Python 在这个场景的分工写论文和做课程设计优先 Matlab图画得漂亮、导师能直接打开要往实际 EMS 系统里塞调度逻辑优先 Python部署省心。两条路我都维护过同一套算例结论是数据必须是同一份 CSV约束必须逐条对上。具体的对账方法放在 4.4 节这里先记住一个原则先在退化算例上比较再上完整模型否则对不上账的时候你分不清是数据问题还是模型问题。4. 双语言代码跑通最小算例从统一 CSV 到跑出调度曲线这一章给出能直接跑的最小算例。数据用一组 24 点的风、光、负荷预测和分时电价算例规模控制在单日任务是把两条语言的代码跑通并对出可复现的成本和曲线。4.1 数据准备一份 CSV 两个语言共用先建数据文件microgrid_day.csv每行一个时段列分别是 hour、P_w、P_pv、P_L、pricehour,P_w,P_pv,P_L,price 1,320,0,180,0.35 2,310,0,160,0.35 3,300,0,150,0.35 ... 24,350,0,200,0.75Matlab 侧用 readmatrix 读Python 侧用 csv 或 pandas 读。注意列名和你代码里的变量一一对应大小写都对上这是我在交流群里看到最多的低级错误来源。两份代码都从这份 CSV 取数不允许在代码里手改任何预测值后面所有对账都以这个文件为准。4.2 Matlab 主程序YALMIP 完整跑通%% 微电网日前调度主程序Matlab YALMIP Gurobi % 单位约定功率 kW能量 kWh时间步长 1 h T 24; dt 1; % 调度周期与步长 E_rated 600; SOC0 0.5; % 储能容量与初始 SOC eta 0.95; % 单程充放电效率 % —— 数据区换数据只改这里 —— data readmatrix(microgrid_day.csv, NumHeaderLines, 1); P_w data(:,2); P_pv data(:,3); P_L data(:,4); price data(:,5); % —— 决策变量 —— P_buy sdpvar(1,T); P_sell sdpvar(1,T); P_ch sdpvar(1,T); P_dis sdpvar(1,T); Pc_w sdpvar(1,T); Pc_pv sdpvar(1,T); SOC sdpvar(1,T1); u_ch binvar(1,T); u_dis binvar(1,T); % —— 约束组装 —— Constraints [SOC(1) SOC0, SOC(T1) SOC0]; % 日始末对齐 for t 1:T Constraints [Constraints, ... SOC(t1) SOC(t) (eta*P_ch(t) - P_dis(t)/eta)*dt/E_rated, ... 0 P_ch(t) 300, 0 P_dis(t) 300, ... 0 SOC(t1) 0.9, ... u_ch(t) u_dis(t) 1, ... P_ch(t) 300*u_ch(t), P_dis(t) 300*u_dis(t), ... 0 P_buy(t) 800, 0 P_sell(t) 500, ... P_w(t) - Pc_w(t) P_pv(t) - Pc_pv(t) P_dis(t) P_buy(t) ... P_L(t) P_ch(t) P_sell(t), ... 0 Pc_w(t) P_w(t), 0 Pc_pv(t) P_pv(t)]; end Objective sum(price.*P_buy - 0.8*price.*P_sell ... 0.2*(P_chP_dis)*dt); % 购电 - 售电 吞吐损耗 ops sdpsettings(solver,gurobi,verbose,1); sol optimize(Constraints, Objective, ops); % 取结果画图 P_buy_v value(P_buy); P_dis_v value(P_dis); SOC_v value(SOC); plot(1:T, SOC_v(1:T), LineWidth, 1.5);说明一下参数来路。eta 取 0.95 是磷酸铁锂系统往返效率折算到单程的典型值铅酸电池改 0.85。300 kW 是 PCS 变流器功率600 kWh 配 300 kW 等于 0.5C 充放倍率是储能电站最常见的配置。800/500 kW 来自并网变压器容量实际项目按接入批复数据填。0.2 元/kWh 的损耗系数先按经验值给论文复现可以单独做扫描。售电系数 0.8 意思是上网电价是购电的 80%如果你所在地区补贴高改这个数储能套利行为会明显变激进。4.3 Python 主程序PuLP 平行实现# 微电网日前调度主程序Python PuLP CBC import csv, pulp T, dt 24, 1 E_rated, SOC0, eta 600, 0.5, 0.95 rows list(csv.reader(open(microgrid_day.csv))) raw [[float(x) for x in r] for r in rows[1:]] P_w, P_pv [r[1] for r in raw], [r[2] for r in raw] P_L, price [r[3] for r in raw], [r[4] for r in raw] prob pulp.LpProblem(Microgrid_Dispatch, pulp.LpMinimize) P_buy pulp.LpVariable.dicts(P_buy, range(T), 0, 800) P_sell pulp.LpVariable.dicts(P_sell, range(T), 0, 500) P_ch pulp.LpVariable.dicts(P_ch, range(T), 0, 300) P_dis pulp.LpVariable.dicts(P_dis, range(T), 0, 300) Pc_w pulp.LpVariable.dicts(Pc_w, range(T), 0, None) Pc_pv pulp.LpVariable.dicts(Pc_pv, range(T), 0, None) SOC pulp.LpVariable.dicts(SOC, range(T1), 0, 0.9) u_ch pulp.LpVariable.dicts(u_ch, range(T), catBinary) u_dis pulp.LpVariable.dicts(u_dis, range(T), catBinary) prob SOC[0] SOC0 prob SOC[T] SOC0 # 日终回到初始 SOC for t in range(T): prob SOC[t1] SOC[t] (eta*P_ch[t] - P_dis[t]/eta)*dt/E_rated prob u_ch[t] u_dis[t] 1 prob P_ch[t] 300*u_ch[t] prob P_dis[t] 300*u_dis[t] prob (P_w[t] - Pc_w[t] P_pv[t] - Pc_pv[t] P_dis[t] P_buy[t] P_L[t] P_ch[t] P_sell[t]) prob 0 Pc_w[t] P_w[t] prob 0 Pc_pv[t] P_pv[t] prob pulp.lpSum(price[t]*(P_buy[t] - 0.8*P_sell[t]) 0.2*(P_ch[t] P_dis[t])*dt for t in range(T)) prob.solve(pulp.PULP_CBC_CMD(msg1)) print(pulp.LpStatus[prob.status])这段和 Matlab 版一行一行对应同样的数据来源、同样的 0.8 售电系数、同样的 0.2 吞吐损耗、同样的 SOC 上下限。跑完LpStatus打印Optimal才算通过。如果第一次跑出来不是 Optimal先看 CBC 输出的 message常见原因是脚本保存编码问题——确认文件存成 UTF-8不要在代码里混入全角括号。Python 环境装好之后3.9 以上就够用pip install pulp即完不需要额外配求解器。4.4 运行结果复盘先看三张图和一个成本这类交付包里常见的运行结果.zip解压后通常是三样东西调度功率曲线、SOC 曲线、成本明细表。拿到手别急着看结论按下面顺序复核。第一张图是功率平衡曲线把「发电 放电 购电」和「负荷 充电 售电」各画一条 24 点曲线两条线必须完全重合这是模型正确性最直观的证明。第二张是 SOC 曲线从 0.5 出发回到 0.5中间凸起对应谷时充电凹下对应峰时放电如果 SOC 一直贴上限或下限说明储能参数和电价结构不匹配。第三张是购售电曲线高峰时段大量购电、低谷时段几乎不购才符合分时电价下的经济直觉。一个数字是总运行成本单位元/天后面所有算法对比都以它为准。双语言对账也在这时候做。两个版本跑完成本应该一样SOC 曲线应该重合到像素级。如果对不上按顺序查三个地方CSV 是否同一份、效率方向是否一致充电乘效率、放电除效率很多人只写了一个、约束边界是否写全。先跑一个退化算例——把储能容量设 0、售电关掉——两边必须完全一致再开储能排查范围立刻小一半。5. 微电网调度避坑指南五个高频翻车点与排查顺序以下五条是我在两个实际项目里真踩过、而且排查超过半天的坑按出现频度排序。每条按现象、原因、解决三段写你在复现时可以直接对照。5.1 SOC 初值导致首小时 infeasible现象求解器秒报 INFEASIBLE日志里第一个小时就过不去。原因SOC0 给得太高比如 0.9而当天光伏很强、负荷很低储能被迫维持充电SOC 一旦越过 0.9 上限模型就没有可行解。解决先把 SOC0 改成 0.5 重跑如果还不行把 SOC 上限临时放开到 1.0。这样能区分两种性质不同的问题边界给死还是等式写错。调试完记得把边界改回来。5.2 同一时段既充又放互斥约束漏了现象结果里 P_ch 和 P_dis 在同一个时段同时非零效率白白损耗。原因只写了功率上下限漏了u_ch(t) u_dis(t) 1和它配套的P_ch 300*u_ch关联约束。没有关联约束时二进制变量不起作用。解决把互斥和关联约束补齐。另外别用P_ch * P_dis 0这种乘积约束代替那会让 MILP 变成非凸问题CBC 和 Gurobi 都保证不了全局最优。5.3 求解器报 infeasible先手算三个时段的功率平衡现象模型看哪哪对求解器就是报错还说不清哪条约束冲突。原因功率平衡等式漏项或符号反了这是我见过最多的原因。最常见的是把 P_L 写错边或者弃风弃光变量没有进入等式。解决把前三个时段的决策变量全部固定成一组手算值代入等式逐项检查。在 Matlab 里用check(Constraints)能定位具体冲突约束在 Python 里可以临时注释掉功率平衡约束看求解状态变化。手算三个点比看任何报错都直接。5.4 双语言结果对不上先查数据口径现象Matlab 算出一天成本 1234 元Python 算出 1567 元差了 27%。原因两条语言读的数据文件不是同一份或者同一列被当成不同单位处理。我遇到过有人在 Matlab 里把功率当 MW在 Python 里当 kW结果储能几乎不动作。解决统一 CSV 是唯一的正确姿势并且每条语言从文件读数、不在代码里手工改数值。先跑无储能、无售电的退化算例两边的购电曲线必须逐点一致再开储能。5.5 储能模型不动寿命损耗系数与价差的关系现象储能容量很大但调度结果里 SOC 一直贴在 0.1 下限附近储能几乎不充不放。原因两种情况。一是目标函数里没写损耗项存在多个等价最优解求解器选了一个储能闲置的解二是损耗系数 0.2 元/kWh 设得过高峰谷价差 0.45 元填不平来回吞吐 0.4 元的损耗储能不动作本来就是经济最优。解决对损耗系数做 0.05~0.5 元/kWh 的扫描看 SOC 从哪个值开始活跃。如果价差只有 0.2 元储能不动很正常别急着怀疑代码这正是模型在告诉你经济账算不过来。6. 进阶实战把日前调度改成滚动优化的三处改动日前调度是开环用今天的预测排明天的表预测错就错到底。项目一旦要落地几乎都要升级成滚动时域rolling horizon或模型预测控制MPC。改动点只有三处核心的 MILP 模型一行都不用推倒重来。第一处固定窗口改滑动窗口。把「一次性排 24 点」改成「每个时段 h基于最新预测排 h 到 h23 的窗口但只执行第一个时段的决策」然后滚动到 h1。伪代码就三行for h in range(24): prob_h build_model(h, horizon24) # 从 h 起排 24 点数据换成最新预测 prob_h.solve() schedule[h] P_buy[h].value() # 只落第一个时段的决策第二处末端 SOC 从硬约束改成软目标。开环模型里SOC(T1) SOC0是硬约束滚动优化里如果每一轮都锁死末端会导致储能为了按时回到 0.5 而在最后几小时做不经济的动作。常见做法是把它改成软约束目标函数里加一项末端 SOC 偏离惩罚或者设定一个随窗口平移的目标值让储能行为由价差驱动而不是由终点驱动。第三处回测验证。用历史数据把开环和滚动各跑一个月对比总成本和约束违反次数。滚动方案总成本通常略高于开环——因为开环用了「完美预测」这不代表滚动更差反而说明对预测误差更鲁棒。判断标准看两条SOC 是否全程在界内、高峰是否有功率越限。我现在的习惯是任何调度代码先跑开环版再在同一套数据和函数上改滚动版两个结果一起存档。这样导师或甲方问起来能讲清楚「预测完美时成本多少、预测有误差时成本多少」比只给一张好看的曲线有说服力得多。希望帮到你。本文还有配套的精品资源点击获取
返回列表