ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

微网优化调度模型:基于MATLAB+YALMIP的日前调度与储能策略详解

微网优化调度模型:基于MATLAB+YALMIP的日前调度与储能策略详解 做微网项目的工程朋友应该都有这个体会设备装上了DCS和EMS里的控制策略却往往停留在“峰放谷充、光伏优先”这类固定逻辑。遇上电价、天气、负荷三者组合得很复杂的时候手里有光伏、储能、燃气轮机的微网到底怎么排布才能真的省钱靠直觉是排不清楚的。我的做法是把“怎么排布”这个问题写成微网优化调度模型以全天运行成本最小为目标把分布式电源出力、储能充放电功率、跟主网购售电的功率一起丢给 MATLAB YALMIP 去求解。这篇文章就把这个模型从数学建模到代码实现、再到调参排错的全过程完整拆解一遍。无论你是电力方向的学生在做毕业设计还是园区能源工程师在写能量管理策略都能直接照着做出来。1. 微网调度模型要回答的问题每台设备该出多少力、储能何时充放1.1 模型覆盖的设备与典型应用场景微网优化调度的大前提是先把系统拓扑固定下来。我常用的模型考虑了这几种设备光伏、风电出力主要看气象条件模型里作为不可调度的预测值给定一条24小时的时序曲线不需要做决策。这类电源本质上是“看天吃饭”。微型燃气轮机或柴油发电机真正可调度的电源有出力上下限、爬坡速率限制运行还要烧燃料。电池储能既能从母线吸收电能也能向母线释放电能但运行区间有充电功率上限、放电功率上限、SOC上下限还要考虑充放电效率。主网联络线并网型微网可以从上级电网购电也可以把本地多余电量出售但交换功率不能超过联络线容量。这种模型最常见的落点是园区型微网和工商业储能的运行策略优化。举个例子一个厂房屋顶装了500 kW光伏配电房并了一套1 MW/2 MWh储能柜备用一台200 kW燃气轮机还有一条10 kV联络线。业主最关心的就是“明天怎么安排燃气轮机、储能和购售电才能把总电费降到最低”。这个问题翻译成数学模型就是日前优化调度。模型内部我一般不把储能、光伏的逆变器动态过程放进去因为日前调度是小时级的决策它关心的是功率平衡和电量分配不是毫秒级的电压电流控制。逆变器层面的跟踪交给下层控制去完成这里只输出计划值。1.2 固定策略和优化调度的本质差别固定控制策略在工程里流行是因为逻辑简单、可靠。比如经典的“光伏自消纳优先余电充电高峰放电”或者“谷时充电、峰时放电”。这类规则本质上是在决策空间里画好了一条固定路径省去了计算但代价是没有路径选择的自由度。举个例子某天下午电价是负值光伏又大发储能已经97%了。按“光伏自消纳优先”的固定策略要么被迫弃光要么给已经接近满电的储能硬充不管怎么选都亏。优化模型会直接给出一个明显更合理的方案把多余电量卖给电网储能保持高SOC不动。这类跨时段、跨设备的综合判断固定规则很难覆盖。我自己在项目里做过一次对比用同一个园区数据分别跑固定策略和优化调度一个月下来优化模型比固定策略节省约8%到12%的用能成本而且这还是没考虑光伏预测偏差修正的情况。当然固定策略也不是一无是处它胜在不需要依赖预测精度适合做故障情况下的后备控制。1.3 时间尺度、决策变量和模型边界模型默认取24小时、1小时一个点也就是 T 24。如果研究储能参与现货市场或者日内滚动调度可以改成15分钟间隔T 96。T 变大之后模型里的连续变量数量和约束数量线性增长但求解时间往往增长更猛这点后面讲求解器的时候会展开。决策变量分成三类连续变量燃气轮机出力 P_mt(t)、储能充电功率 P_ch(t)、储能放电功率 P_dch(t)、购电功率 P_buy(t)、售电功率 P_sell(t)、储能荷电状态 SOC(t)二进制变量购电/售电互斥标志 z_buy(t)、z_sell(t)固定输入负荷预测曲线、光伏预测曲线、分时电价、设备参数。模型跑完之后你得到的是一组逐小时功率计划。调度员或者EMS根据这份计划去下发指令这就是日前优化调度在整个能量管理系统里的位置。2. 把调度规则翻译成数学语言目标函数与约束拆解2.1 目标函数替“花钱”找到一条最低的路径优化目标设为一天内总运行成本最小包括四块燃气轮机的燃料成本、运维成本、向电网购电的成本再减去售电收益。写成表达式就是min F Σ [ (fuel_price_thermal / η_mt) * P_mt(t) c_om * P_mt(t) price_buy(t) * P_buy(t) - price_sell(t) * P_sell(t) ]这里面最容易看懵的是燃料成本。燃气轮机发 1 kWh 电需要输入 1/η_mt kWh 的燃料热量所以燃料费用 单位热量价格 / 效率 × 发电功率。比如 η_mt 取 0.35单位热量气价折合 0.55 元/kWh那么发一度电的燃料成本就是 0.55 / 0.35 ≈ 1.57 元/kWh。如果热量价格折合得低或者机组效率高燃气发电才可能跟购电竞争。运维成本 c_om 按发电量线性计算一般取 0.03~0.08 元/kWh用来覆盖机油、维护、定期检修的折算费用。储能的充放电损耗不直接出现在目标函数里它通过效率系数影响SOC的递推从而间接决定储能是否值得充放。注意一个细节如果要做电厂级的调度还要考虑机组启停成本。启停成本需要引入二值变量模型就从线性规划变成混合整数规划求解难度会显著提高。文章里的简化模型暂时不展开启停但在实际工程里如果燃气轮机频繁启停这部分成本必须估算进去。2.2 功率平衡所有能量都有去处每个时段的母线功率都要守恒这条约束是整个模型的地基。左右两边的关系是电源侧等于负荷侧加储能充电加售电P_mt(t) P_pv(t) P_wt(t) P_dch(t) P_buy(t) P_load(t) P_ch(t) P_sell(t)左边是给母线注入功率的设备燃气轮机、光伏、风电、储能放电、主网购电右边是消耗功率的负荷、储能充电、向主网售电。写约束时P_ch 和 P_dch 必须各自定义为非负变量不能用一个有符号变量代表“充电为正、放电为负”。如果偷懒用一个变量SOC递推和功率平衡虽然形式上还能写但模型会利用正负同时出现的漏洞得到“边充边放”的伪解。2.3 储能SOC递推一个约束连接全天状态储能系统的状态转移是小时级的SOC(t1) SOC(t) η_ch * P_ch(t) * dt / Cap - P_dch(t) * dt / (η_dch * Cap)这里的关键是充电效率和放电效率分别独立。充电的时候电网输入的电能有一部分变成热量真正存进电池的只有 η_ch 倍放电的时候电池释放的能量同样要打一个 η_dch 的折扣才能变成母线侧实际用到的功率。两个效率通常都取 0.92~0.98具体看电池厂商给的参数。SOC本身上下限约束SOC_min ≤ SOC(t) ≤ SOC_max初始条件和日末条件SOC(1) SOC_initSOC(T1) SOC_end很多简化模型会把 SOC_end 直接设成等于 SOC_init目的就是让储能“今日事今日毕”避免模型为了赚当天最后的峰谷价差把电池能量全部放空导致第二天没法正常运行。在实际做滚动优化的时候日末SOC可以根据后续天的预测来设定不一定要等于初始值但单日日前调度里设成相等最稳妥。2.4 机组出力和购售电互斥不让模型做出违背物理的操作燃气轮机约束就是一组上下限和爬坡限制P_mt_min ≤ P_mt(t) ≤ P_mt_maxP_ramp ≤ P_mt(t1) - P_mt(t) ≤ P_ramp爬坡限制的步长是dt如果你把dt从1小时改成15分钟爬坡速率就要相应缩小不然一小时内能跑的距离会算错。这点我在项目里踩过坑后面详说。购售电互斥约束是很多初学建模的人容易漏掉的部分P_buy(t) ≤ P_grid_max * z_buy(t) P_sell(t) ≤ P_grid_max * z_sell(t) z_buy(t) z_sell(t) ≤ 1 z_buy(t), z_sell(t) ∈ {0,1}为什么要加互斥理论上当购电价永远高于售电价时数学模型不会傻到同时买和卖。但在某些电力市场场景下比如低谷时段电价变负如果没有互斥约束模型就会玩“低价买入再高价卖出”的跨时段套利这在实际物理系统里大概率是不允许的。加了互斥之后模型决策里就天然带上了“联络线同一时段只能单向送电”的属性。3. 在MATLAB里用YALMIP建模从变量声明到求解3.1 用YALMIP建模到底在做什么很多没有接触过YALMIP的朋友会问MATLAB里明明有intlinprog为什么还要套一层YALMIP我的体会是直接用intlinprog要把所有时段的所有变量拍扁成一维向量然后把每一条约束都转换成A矩阵的行这个工作重复、枯燥、容易错。T24还好T96或者再加入机组启停、需求响应手拧矩阵会把人逼疯。YALMIP做的事情就是让你用贴近数学表达式的符号语言来建模。你用sdpvar声明连续变量用binvar声明二进制变量然后直接写约束表达式和累加目标函数最后调optimize()。它负责解析表达式、把模型整理成求解器需要的标准形式并调用底层求解器你只需要管理好“变量→约束→目标”这一层逻辑。换句话说YALMIP把你从“矩阵排版工”的角色里解放出来让你能专心调模型本身。3.2 核心代码变量、约束、目标、求解这里给出一个可以跑的简化模型代码骨架数据填的是示意值。整个流程分四步声明参数、声明变量、堆约束、求解。%% 微网日前优化调度模型示意数据 clear; clc; T 24; dt 1; % 24小时1小时间隔 %% 1. 输入数据请替换为自己的预测曲线 % 单位统一功率kW能量kWh电价元/kWh P_load [310 280 260 250 255 270 350 480 560 540 500 470, ... 460 480 520 550 570 580 540 480 420 380 340 320]; % 负荷 P_pv [0 0 0 0 10 60 150 260 360 420 450 430, ... 400 350 280 180 90 20 0 0 0 0 0 0]; % 光伏预测 P_wt 30 * ones(1, T); % 风电预测 % 分时购电价元/kWh高峰贵低谷便宜 price_buy 0.5 * ones(1, T); price_buy(9:12) 0.9; price_buy(18:20) 0.9; price_sell 0.3 * ones(1, T); price_sell(9:12) 0.5; price_sell(18:20) 0.5; %% 2. 设备参数 % 燃气轮机 MT_Pmin 50; MT_Pmax 500; fuel_price_thermal 0.55; % 天然气折算到每kWh热量的价格 元/kWh eta_mt 0.35; % 发电效率 c_om 0.05; % 运维成本 元/kWh % 储能 Cap 1000; % 容量 kWh SOC_min 0.2; SOC_max 0.9; SOC_init 0.5; eta_ch 0.95; eta_dch 0.95; Pch_max 300; Pdch_max 300; % 主网联络线 Pg_max 500; % 最大交换功率 kW %% 3. 声明决策变量 P_mt sdpvar(1, T); % 燃气轮机出力 P_ch sdpvar(1, T); % 储能充电功率 P_dch sdpvar(1, T); % 储能放电功率 P_buy sdpvar(1, T); % 购电功率 P_sell sdpvar(1, T); % 售电功率 SOC sdpvar(1, T1); % 储能荷电状态 z_buy binvar(1, T); % 购电标志 z_sell binvar(1, T); % 售电标志 %% 4. 约束条件 C []; % 功率平衡 C [C, P_mt P_pv P_wt P_dch P_buy P_load P_ch P_sell]; % 燃气轮机 C [C, P_mt MT_Pmin, P_mt MT_Pmax]; % 储能SOC递推 C [C, SOC(2:T1) SOC(1:T) (eta_ch*P_ch - P_dch/eta_dch)*dt/Cap]; C [C, SOC SOC_min, SOC SOC_max]; C [C, SOC(1) SOC_init, SOC(T1) SOC_init]; % 日末回到初始SOC C [C, P_ch 0, P_ch Pch_max]; C [C, P_dch 0, P_dch Pdch_max]; % 购售电互斥 C [C, P_buy 0, P_buy Pg_max*z_buy]; C [C, P_sell 0, P_sell Pg_max*z_sell]; C [C, z_buy z_sell 1]; %% 5. 目标函数 Objective sum((fuel_price_thermal/eta_mt c_om) * P_mt ... price_buy.*P_buy - price_sell.*P_sell); %% 6. 求解 ops sdpsettings(solver, gurobi, verbose, 1); sol optimize(C, Objective, ops);跑完之后如果sol.problem 0就可以用value(P_mt)、value(SOC)这些命令把结果取出来画图。如果返回1说明模型无可行解排查方法留到第5节讲。3.3 从代码到结果的执行流程这段代码里有两个容易让人困惑的细节我单独说明一下。第一个是SOC为什么会声明成T1个变量而不是T个。因为递推约束 SOC(t1) 和 SOC(t) 构成跨时段联系如果只声明T个变量就要用 SOC(1:T) 和 SOC(1:T-1) 做递推最后缺少对末状态的显式描述写起来反而不方便。我习惯直接声明SOC sdpvar(1, T1)让初始状态和24个时段的最终状态都有明确位置。第二个是目标函数里price_buy.*P_buy用了点乘。YALMIP在解析表达式的时侯对维度很敏感如果P_buy是1×T的行向量price_buy也必须是1×T的行向量点乘或者直接写成price_buy * P_buy都行但别混用行向量和列向量。我见过很多新手在这里报维度错误。3.4 环境配置新版本MATLAB安装YALMIP和Gurobi的注意事项YALMIP本身是一个工具箱下载解压后在MATLAB里把对应文件夹加入搜索路径就行。注意不要只把文件夹放在当前目录因为换一个工作目录就找不到了。正确做法是addpath(genpath(D:\Tools\YALMIP))然后savepath保存下来。Gurobi的安装稍微绕一点。先安装Gurobi本体然后在MATLAB里运行它自带的MATLAB接口配置脚本或者手动把gurobi安装目录里的matlab子目录加进搜索路径。装了新版本MATLAB之后一些老版本YALMIP可能会出现内部报错建议大家直接从官方GitHub仓库拿最新的release旧版在2023b之后的环境里偶有兼容问题。验证配置是否成功最直接的办法是运行yalmiptest或者在命令行里执行一个1变量问题并指定gurobi求解器。能秒出结果说明YALMIP和Gurobi的通道已经打通。如果提示找不到gurobi多半是环境变量没配好检查一下GUROBI_HOME。4. 求解器选型与MIP调参为什么Gurobi往往是第一选择4.1 求解器对比Gurobi、CPLEX、SCIP、intlinprog建模完成之后真正求解MILP的是底层求解器。不同求解器对同一个模型的求解速度差别很大尤其是当模型里有几十上百个二进制变量时。求解器许可证微网MILP表现推荐场景Gurobi商业学术免费很强默认参数下就能快速收敛工程和研究首选CPLEX商业学术免费强老牌稳定部分项目规范里指定要用SCIP开源中等模型稍大明显变慢预算有限、非商业用途intlinprogMATLAB自带小规模可用大规模吃力教学演示、快速验证我的项目里绝大多数场景都直接用Gurobi。同样的24时段、带互斥约束的模型Gurobi基本一秒内出最优解而intlinprog可能要跑十几秒甚至更久模型再大一点差距会更明显。学术和商用许可方面Gurobi对高校和科研用户免费很多学生团队都会装。4.2 MIP gap和求解时间的工程权衡微网日前调度问题规模不大24小时、1台机组、1个储能、1条联络线二进制变量只有48个Gurobi默认参数能很快搜到最优。但如果你把模型扩展成96时段、再加机组启停或者做多场景随机规划二进制变量会成倍增长求解时间可能从几秒跳到几分钟。这时我会主动放宽MIP gapops sdpsettings(solver, gurobi, mipgap, 0.01, timelimit, 120);mipgap0.01意思是允许结果与最优目标相差1%以内就提前终止。对微网调度这种工程决策问题1%的最优性差距完全可以接受换来的是求解时间几个数量级的下降。如果跑的是96时段滚动优化我甚至会把gap放宽到0.05因为每次滚动只取第一个时段下发整体影响很有限。4.3 数值尺度最容易忽略但影响巨大求解器内部有数值容差一般容忍约束违反程度在10^-6到10^-9的量级。模型里的系数如果忽大忽小比如电量用MWh成本用分价格用厘就会导致约束矩阵里的数值病态求解器可能直接判定某个可行解为不可行。我建议在模型里统一单位功率用kW能量用kWh电价用元/kWhSOC是无量纲0~1。这样所有参数量级都落在0.01到10000之间求解器的数值稳定性最好。还有一个经验P_ch_max 和 P_dch_max 不要设成10的幂次又乘以0.97之类的比值保持在整百附近对数值更友好。5. 模型跑不出来从无可行解到结果不合理的排查链路5.1 sol.problem返回1一步步压缩出无可行解的原因写代码时不是屏幕上没报错就万事大吉。建议每次求解后都检查sol.problem0代表最优1代表无可行解2代表求解器内部错误。如果sol.problem 1先别急着改约束用“约束分离法”排查先把目标函数设成0只保留功率平衡和变量边界看能不能求解。不能的话问题出在边界条件或者功率平衡。加入燃气轮机约束重新求解加入SOC递推和SOC边界重新求解逐步累积直到某一步问题从“可解”变成“无可行解”那一组约束就是嫌疑区。我实际调试中最快的一次是发现SOC(1) SOC_init 和 SOC(T1) SOC_init 同时加上而储能容量太小、放电功率又太大导致最后两个小时无论怎么放都回不到初始值。把日末约束放成SOC(T1) SOC_end问题立即解决。这种病在物理上有道理储能本来就该留有余量过夜。5.2 结果“貌似合理”但实际错误的常见病症sol.problem 0不代表结果符合直觉。下面几种情况我几乎每隔一阵就会遇到一次所有时段都在放电最后一小时SOC跌到下限。多半是没设日末SOC约束模型为了多赚利益把电池清空。加SOC(T1) SOC_init或者相等约束。储能整个周期一动不动。先别怀疑代码看看分时电价是不是恒定的。如果购电价和售电价全天都一个值储能没有套利空间不动是正常结果。燃气轮机全天贴着上限跑。这往往是燃料成本比购电价还便宜导致的。要么是电价数据填错要么是你没考虑机组频繁启停对寿命的影响。工程上一般会在目标函数加一个运行小时数惩罚。某个时段出现P_ch和P_dch同时为微小正数。这是数值求解的伪量把互斥约束加上或者在结果后处理时统一做阈值清洗。5.3 一个真实案例单位混乱怎么骗过了眼睛有一次我在做一个园区光储项目夜里跑出来的结果特别怪异白天光伏充足的时候储能在“飞速充电”SOC曲线陡得吓人。我一开始怀疑是预测数据不对后来盯着代码看了十分钟才发现电池容量字段填的是1注释写的却是MWh而光伏、负荷全用的kW。整个模型里Cap的量级差了1000倍储能递推方程算出来就跟开了倍速一样。从那之后我习惯在参数区开头写一行加粗注释所有功率单位kW能量单位kWh。新加任何参数都先过一遍单位再编译约束这条习惯帮我省掉了大量排错时间。6. 从调度计划到工程闭环结果读取、画图与模型扩展6.1 value()取解与绘图把求解结果变汇报图求解完成后用value()把决策变量取出来P_mt_opt value(P_mt); P_ch_opt value(P_ch); P_dch_opt value(P_dch); P_buy_opt value(P_buy); P_sell_opt value(P_sell); SOC_opt value(SOC);画图建议用堆叠面积图而不是折线图因为要展示的是能量流动关系堆叠后能一眼看出功率平衡是否成立。我最常用的画法是一个双面板图上面画各电源出力曲线和负荷曲线的平衡关系下面画SOC曲线。调度人员看到SOC在谷段充电、峰段放电不需要太多解释就能懂模型的价值。如果是要汇报给管理层我还会额外标注每个时段的总成本、购售电费用拆分把目标函数里的每一项单独可视化。这个会把“成本最小化”落到实处。6.2 从日前调度到滚动优化可以继续扩展的四个方向这个骨架模型的价值在于它好改。实际工程里我按下面这四个方向扩展过它不确定性建模光伏和负荷预测必然有误差把单一预测曲线改成多个场景做场景随机规划或者用两阶段鲁棒优化处理最坏情况。模型规模变大但对调度方案的稳健性提升非常明显。把储能寿命损耗放进目标函数加一个与充放电量成正比的惩罚项模型就不会毫无节制地让电池每天深充深放。这个扩展对储能参与电力市场的项目尤其重要。需求响应把可平移负荷设成可调变量加在一部分固定负荷之外模型会给出“什么时候该让充电桩降功率”这类建议。多微网互联把单微网功率平衡扩展成多节点模型微网之间的交互功率变成决策变量就是区域多微网协同优化。我做项目时还有一个长期习惯先跑通这个最简模型保证数据管道没问题再一步步往里面加复杂度。直接上完整模型一旦无可行解排查起来非常痛苦从简到繁每一步都能验证反而更快得到正确结果。
返回列表