ARTICLE DETAIL

资讯详情

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

基于两阶段控制的多能源微网协同调度MATLAB实现与仿真要点解析

基于两阶段控制的多能源微网协同调度MATLAB实现与仿真要点解析 去年接了个多能源微网优化调度的项目要求把所有算法都落到MATLAB环境下跑仿真。我一开始觉得这活儿不难公式都有了照着抄就行。真正做下来才发现把一个“基于两阶段控制框架的多能源微网协同自主优化调度系统”从论文里搬到MATLAB实现与仿真运行坑比想象中多得多。今天不聊理论推导就说说这套系统在MATLAB环境下从建模、写代码、调参到结果分析的全过程以及我踩过的几个实实在在的坑。这个系统解决的其实是微网运行中的老问题源荷不确定、多能耦合、设备协同、上级电网交互挤在一起容易顾此失彼。两阶段控制框架的价值在于把“未来一天怎么跑”和“当下十五分钟怎么调”分开处理既保住经济性又兜住安全性。适合正在做微网调度、综合能源系统优化、或者准备用MATLAB做仿真毕设/项目的人。下面直接进正题。1. 为什么微网调度要设计成“两阶段”先定大方向再修小偏差1.1 可再生能源出力不确定性是“分阶段”的根源光伏和风电的预测误差是微网调度最难搞的事情。早上预测中午光伏出力100kW结果天上一片云实际只有60kW。如果你按照100kW去做功率平衡这时候电就不够了如果你全按保守预测来调度得多买很多高价电经济性很差。单阶段调度面对这种不确定性时只能“赌”赌赢了省成本赌输了就是弃负荷或者被考核。两阶段控制框架的思路本质上和日常出差做安排一样出发前按行程表订车订酒店这是第一阶段的“粗计划”到了当天发现航班延误或者路况堵车临时改路线、换交通工具这是第二阶段的“细调整”。微网调度也是这样第一阶段基于日前预测决定机组启停和大部分可预安排的交易第二阶段基于日内实测数据和更新的超短期预测做滚动修正。这样做的好处是把不确定性带来的影响控制在第二阶段的小范围调整里而不是让整个调度计划推倒重来。1.2 两阶段到底分的是哪两个阶段日前调度与日内滚动这套系统里两阶段控制框架的具体落地方式是“日前调度 日内滚动优化”。日前调度的时间尺度一般是1小时优化24小时输出的是机组启停状态、储能充放电的参考轨迹、与上级电网的交换功率计划。日内滚动优化的时间尺度通常是15分钟或者5分钟预测时域可以取2到4小时每到一个采样时刻就用最新的实测负荷、光伏出力、电价刷新预测重新求解一次优化问题但只执行第一个时段的动作。这两个阶段的决策变量和约束不完全一样。日前阶段由于要决定机组的启停往往包含0/1整数变量日内阶段在启停已经定的前提下主要调整机组出力、储能功率和购售电功率以连续变量为主。这种分层方式也暗合控制理论里的“前馈反馈”逻辑日前是前馈日内是带反馈校正的滚动优化。工程上经常把日内滚动优化直接做成模型预测控制MPC后面第3节我会展开说。1.3 “协同自主”不是空话多能源耦合怎么影响调度决策“协同自主”这四个字落到模型里就是耦合约束和目标函数里的协调项。多能源微网不是简单的“电微网”它通常包含电、热、冷、气多种能源形式。比如热电联产机组CHP发电的同时产热电锅炉可以把低价电转化成热储存在蓄热罐里热泵既供暖又能制冷。这些设备之间的耦合关系决定了我们不能把电力调度和热力调度分开做。举个例子一个园区微网里有一台燃气轮机和一台燃气锅炉。如果在电价高峰时段让燃气轮机多发电蒸汽余热就可以替代燃气锅炉供热看起来“多发了电”实际上整体燃料成本可能反而更低。这种跨能源品种的协同优化必须在同一个模型里同时求解电功率平衡和热功率平衡并且加上CHP的“电-热运行域”约束才能真正把协同价值算出来。这也是为什么这个系统的仿真必须建立在多能源模型的数学化基础上而不是简单的电力潮流计算。2. 建模是第一道坎把光伏、储能、燃气轮机都变成线性约束2.1 多能源微网里的设备清单与输入数据准备在做MATLAB实现之前第一步不是写代码而是把所有设备抽象成数学模型。以我常用的一个园区级多能源微网为例设备清单包含以下内容:设备能量形式典型容量关键参数光伏PV电300kW预测出力曲线弃光惩罚单价风电WT电200kW预测出力曲线弃风惩罚单价燃气轮机CHP电热200kW电/180kW热发电效率、热电比、爬坡速率燃气锅炉热500kW热效率电锅炉热300kW电热转换效率电储能ESS电200kWh/100kW充放电效率、SOC范围、循环寿命约束蓄热罐TESS热400kWh热损系数、容量约束上级电网电联络线300kW分时电价、购售电功率上限输入数据一般包括典型日负荷曲线电/热/冷、新能源出力标幺值曲线或用Homer、实测数据折算、分时电价、天然气价格、各设备的初始状态。MATLAB里我习惯把所有原始数据放Excel或CSV里用readtable读进来再统一转成结构体避免代码里到处是散装变量。这里有个经验新能源出力曲线不要直接用“单条曲线”做日前规划最好根据历史预测误差生成几组场景哪怕是简单的“预测值±偏差”也算一个粗糙的鲁棒处理。如果做的是两阶段随机优化场景就更重要了。2.2 目标函数怎么写经济性、低碳性与惩罚项的博弈多能源微网调度的目标函数大部分人第一反应是“总运行成本最小”。没错但实际写起来有几项容易漏。以我常用的目标函数为例总成本包含这么几块向上级电网购电费用分时电价乘以购电功率按时间段累加天然气燃料费用CHP和燃气锅炉消耗的气量乘气价设备运行维护费用按出力或启停次数计弃风弃光惩罚为了不让求解器随意扔掉新能源要给它一个惩罚成本且这个惩罚成本必须高于其他可调节手段的边际成本否则模型会主动弃电启停成本这个一定要加不然优化结果会出现机组每5分钟启停一次的“抖震”现象如果不光算经济账还要反映低碳可以再加碳排放成本项但实际操作中碳价参数不好设我一般把它折算成燃料成本系数避免额外引入非线性。目标函数写成数学形式大概是minimize Σ [ C_buy(t)·P_buy(t) C_gas·G(t) Σ C_om·P_dev(t) C_pen·(P_pv_avail(t)-P_pv_use(t)) C_start·u_start(t) ]这里的u_start是启动状态变量用相邻时刻的启停变量做差得到。如果要在MATLAB里用YALMIP写就是一堆sdpvar和binvar的组合具体代码在第4节。2.3 最容易写错的约束储能SOC、爬坡与联络线功率建模中最能暴露经验差距的是约束条件。很多初学者写的储能SOC递推公式是SOC(t) SOC(t-1) η_ch·P_ch(t) - P_dis(t)/η_dis这句话看着对但至少有三个坑。第一时间间隔可能不是1小时。如果日内阶段时间分辨率是15分钟那么电量变化要乘上Δt否则一天算下来能量守恒都不成立。第二储能不能同时充放电如果不加约束优化器可能会有“一边充电一边放电”的套利行为这在物理上是不可能实现的。解决办法是加二进制变量b_ch和b_dis让两个功率互斥或者用一组大M约束把充放电限制在互补区间。第三SOC的初值怎么给、末值约束要不要加直接影响日前计划的可行性。我一般是设置SOC(1)0.5然后加SOC(T)0.4之类的末值约束保证第二天还能继续用。爬坡约束也容易踩坑。燃气轮机的爬坡率通常是“每分钟多少kW”但调度周期是小时或15分钟必须换算成“每时段能变化多少kW”。如果不考虑时间尺度直接写差值上限小步长情况下爬坡约束会过紧导致无解大步长情况下又过松失去约束意义。联络线功率约束同样要注意方向购电为正、售电为负上限值要区分“从电网买”和“卖给电网”两个方向很多模型只写一个-P_max P_grid P_max这没问题但分时电价下可能因为购售电价差而产生套利如果系统不允许反送电就要额外把售电视为0或用二进制约束。这些约束看着琐碎却是“仿真结果能不能看”的分水岭。我在调试过程中发现90%的不可行解问题都出在SOC递推公式、时间系数和启停状态约束这三个地方。3. 两种落地路线集中式MILP和分布式ADMM我为什么先选集中式3.1 YALMIPCPLEX/Gurobi搭建两阶段MILP在两阶段控制框架下最直接的落地方式是用混合整数线性规划MILP求解。MATLAB里我推荐用YALMIP作为建模层再配CPLEX或Gurobi作为求解器。YALMIP的语法非常贴近公式比直接用求解器API写约束省一半时间。CPLEX和Gurobi都是商业求解器校园许可或者试用版都能用如果你手头没有也可以用intlinprog做备选但求解速度和大规模场景下的稳定性就差远了。为什么这里要用MILP而不是简单的线性规划因为第一阶段要做机组启停决策启停就是0/1整数变量。只要整数变量一出现问题就变成了MILP。两阶段随机优化更复杂它会在第二阶段对每个场景分别建立子问题最终拼成一个大规模MILP。在MATLAB里YALMIP能很方便地把这些场景矩阵化但要注意场景数增多后求解时间是指数增长的所以场景生成要做削减比如用k-means聚类或者后向场景削减算法。集中式MILP的优点是一步到位所有约束在一棵模型树里全局最优性有保证。缺点也很明显一是需要中心节点收集所有设备的数据二是大规模问题求解慢。对园区级微网来说设备数量一般不超过几十台集中式完全够用。3.2 第二阶段滚动修正的一种实现模型预测控制日内滚动优化我最推荐用模型预测控制来实现。这个名字听起来高级其实核心就三步预测模型、滚动优化、反馈校正。放到微网调度里每个控制时刻比如每15分钟用当前实测的光伏、负荷、电价信息更新未来几小时的预测曲线以更新后的预测为输入求解一个带约束的优化问题得到未来预测时域内每个时段的最优控制指令但只执行第一个时段的指令到下一个时刻再重新滚动求解。这个思想用MATLAB实现就是把第1阶段的模型抽出来把时间索引从固定24小时改成滚动窗口。伪代码是这样的%% 日内滚动修正主循环 T_sim 96; % 一天96个15min点 H 8; % 滚动预测时域未来2小时 u_rt zeros(n_gen, T_sim); for t 1:T_sim % 更新未来H时段的预测数据 [P_pv_fc, P_load_fc, price_fc] update_forecast(t, H); % 构建并求解MPC子问题 [u_opt, P_opt] solve_mpc_subproblem(t, H, P_pv_fc, P_load_fc, price_fc); % 只执行第一个控制量 u_rt(:, t) u_opt(:, 1); end这里有个容易被忽略的点MPC子问题求解时预测时域内的状态初值必须用当前时刻的实际状态。比如储能SOC不能从头算要用上一时刻执行完控制动作后的SOC作为初值。否则MPC和开环优化没有任何区别反馈校正就名存实亡了。3.3 如果想做分布式协同ADMM思路与MATLAB中的实现要点如果你面对的是多个微网互联或者微网内部几个主体比如多个楼宇不愿意把全部数据交给中心节点集中式方法就有点吃力了。这时可以考虑分布式协同优化ADMM交替方向乘子法是首选。它的思想是原问题可以拆成几个子问题每个子问题只跟本地数据和部分耦合变量相关通过迭代交换边界变量来逼近全局最优。在MATLAB里做ADMM我建议先从“小问题”试起。比如把一个大微网拆成“电网络子问题”和“热网络子问题”两者之间通过CHP的电热耦合功率连接。每个子问题用YALMIP单独建模然后在迭代中更新拉格朗日乘子和耦合变量。核心更新公式就是x_k1 argmin_x [ f_i(x) ρ/2 · ||x - z_k λ_k/ρ||^2 ]z_k1 argmin_z [ g(z) ρ/2 · ||x_k1 - z λ_k/ρ||^2 ]λ_k1 λ_k ρ·(x_k1 - z_k1)每次迭代都需要调用一次MATLAB优化求解器。这里要特别提醒ADMM对参数ρ的取值很敏感ρ太小可能导致迭代发散ρ太大收敛慢而且它对非凸问题比如包含0/1变量的收敛性没有保证一般只能得到“工程上可接受”的解。所以我的建议是能做集中式就先做集中式把集中式结果当基准再去验证分布式算法有没有跑偏。这能省下大量排查分布式逻辑的时间。4. MATLAB核心代码怎么组织从数据Excel到求解器调用4.1 模块划分输入层、模型层、求解层、输出层一套能复用的MATLAB仿真程序最好按层划分目录。我常用的结构是project/ ├── data/ % 输入数据Excel/CSV/原始曲线 ├── models/ % 各设备建模函数build_CHP.m, build_ESS.m 等 ├── solver/ % 主优化模型day_ahead_model.m, mpc_model.m ├── utils/ % 数据处理、场景生成、结果画图工具 └── main.m % 主流程读数据-建模型-求解-结果分析这样做的好处是换一套数据或者换一个设备参数不需要动模型代码只改data里的Excel就行。我的main.m开头通常是clc; clear; close all; %% 读入数据 data readtable(data/microgrid_data.xlsx); price_buy data.price_buy; % 分时购电价 P_load data.load_electrical; % 电负荷 P_pv_fc data.pv_forecast; % 光伏预测出力 %% 初始化设备参数 params init_device_params(); %% 调用日前优化 result_da solve_day_ahead(price_buy, P_load, P_pv_fc, params); %% 调用日内滚动优化 result_rt solve_real_time_rolling(result_da, data, params); %% 画图与结果统计 plot_results(result_da, result_rt);模型函数内部只干一件事接受输入数据构造YALMIP变量和约束调用求解器返回结果结构体。别在一个脚本里把数据、模型、画图全糊在一起否则后期改起来会崩溃。4.2 核心约束代码示例SOC更新、功率平衡、机组出力这里给一段我在日前调度模型里常用的核心约束代码YALMIP语法注释对新手友好%% 日前调度模型核心约束 T 24; % 24小时 n_gen 2; % 2台机组 % 变量定义 u binvar(n_gen, T); % 机组启停 P sdpvar(n_gen, T, full); % 机组出力 P_ch sdpvar(1, T); % 储能充电 P_dis sdpvar(1, T); % 储能放电 P_buy sdpvar(1, T); % 向电网购电 SOC sdpvar(1, T1); % 储能SOC多一个时刻便于递推 % 约束集合 C []; %% 电功率平衡 for t 1:T C [C, sum(P(:,t)) P_dis(t) - P_ch(t) P_pv(t) P_buy(t) P_load(t)]; end %% 储能SOC递推与容量约束 SOC0 0.5; % 初始SOC C [C, SOC(1) SOC0]; for t 1:T % 注意这里以1小时为步长所以没有乘Δt如果步长是15分钟要乘0.25 C [C, SOC(t1) SOC(t) 0.9*P_ch(t) - P_dis(t)/0.9]; C [C, SOC_min SOC(t1) SOC_max]; C [C, 0 P_ch(t) P_ch_max]; C [C, 0 P_dis(t) P_dis_max]; end %% 机组出力与爬坡约束 for i 1:n_gen for t 1:T C [C, P_min(i)*u(i,t) P(i,t) P_max(i)*u(i,t)]; end for t 2:T C [C, P(i,t) - P(i,t-1) ramp_up(i)]; C [C, P(i,t-1) - P(i,t) ramp_down(i)]; end end这段代码里的模型变量都是sdpvarbinvar表示0/1整数变量。实际使用时还需要加上启动状态约束和热功率平衡约束。思路完全相同。4.3 求解设置与结果回读不要让“warning”糊弄过去模型建好之后求解调用是这样的ops sdpsettings(solver, gurobi, verbose, 2, timelimit, 300); diagnostics optimize(C, Objective, ops); if diagnostics.problem 0 P_opt value(P); SOC_opt value(SOC); u_opt value(u); else disp(求解失败问题类型); disp(diagnostics.info); end这里有两个容易犯的错。第一个是diagnostics.problem不等于0时很多人直接忽略继续用value(P)取结果——此时取的可能是NaN或者上一次的结果后处理全乱套。第二个是求解器返回的warning信息比如“数值精度有问题”或者“部分约束被简化掉”这些警告是排查模型的线索不能关掉verbose就假装看不见。我在调试时会把diagnostics.solveroutput完整打印出来查看Gurobi或CPLEX返回的MIP gap和求解状态码。MIP gap如果停在1%以上说明模型还没收敛到最优结果只能作为参考不能直接拿去写报告。5. 仿真结果到底该看什么不只是“总成本下降20%”5.1 搭建可比算例两阶段vs单阶段、协同vs独立一个调度系统的仿真报告最关键的是对比实验设计。光把单次运行成本贴出来没说服力得让读者看到“两阶段相比单阶段好在哪”“协同相比独立好在哪”。我一般设置四组算例算例A单阶段确定性调度只用日前预测不滚动修正算例B两阶段控制框架日前日内滚动算例C独立调度电负荷和热负荷各自满足平衡不考虑CHP热电耦合算例D协同自主优化多能耦合统一调度。然后用同一组历史数据做回放仿真。历史数据要留出预测误差否则两阶段框架的优势体现不出来。我还会给预测曲线加不同水平的误差比如±10%、±20%看看系统在不确定性增大时的表现差异。对比指标通常包括总运行成本、新能源弃用率、联络线功率峰谷差、储能循环次数、机组启停次数。下面是一张我常用的结果汇总表示例算例总运行成本元弃新能源率%联络线峰谷差kW机组启停次数单阶段128508.21806两阶段119304.51205独立调度134209.62108协同优化118603.81055注意这个表里的数字是我在某个项目里的典型结果不代表所有微网都这样。你的仿真只要趋势合理、逻辑自洽数字不同很正常。5.2 从曲线里找毛病SOC锯齿、联络线波动、机组频繁启停仿真跑完之后不要急着截图发朋友圈。我习惯先把几条关键曲线画出来储能SOC曲线、机组出力曲线、联络线交换功率曲线、新能源实发与预测对比曲线。从这些曲线里能看出模型的“性格”。SOC曲线如果出现高频锯齿——一会儿满充一会儿大放——说明目标函数里对储能寿命没有约束或者分时电价变化太剧烈电池被当成套利工具在用。解决思路是加一个“充放电次数/功率变化”惩罚项或者把SOC变化率限制在一个平滑范围内。联络线功率如果出现频繁的大幅波动说明系统在追逐电价波动购售电策略过于激进。实际电网运行中联络线功率波动太大会被上级电网考核。这时候要在目标函数里加上联络线波动惩罚或对相邻时段购电功率变化率设限。机组频繁启停也是常见问题。如果目标函数里没有启停成本MILP求解器会为了满足短时负荷波动而不断启停机组。加了启停成本之后问题会明显缓解。如果还不行可以把最小运行/停机时间约束加上也就是常说的“最小开停机时间约束”但这类约束在YALMIP里写起来略复杂需要辅助变量。5.3 一组可复现的仿真数据与典型输出表格为了让初学者能复现我给一个小型算例的参数建议。假设微网只有一台CHP100kW电、80kW热、一台燃气锅炉300kW热、储能50kWh/25kW、光伏100kW和电网联络线150kW。电价采用峰谷三段峰时1.2元/kWh平时0.7元/kWh谷时0.3元/kWh。天然气价格2.5元/m³。电负荷峰值120kW热负荷峰值200kW。这种小算例MATLABYALMIPGurobi在几秒内就能跑完。运行结束后把各个阶段的决策结果导出成表格我一般会导出这几个Sheet日前计划表每小时机组启停、储能SOC、购电功率、日内修正表每个15min时段的实际出力、SOC、联络线功率、成本明细表购电成本、燃料成本、启停成本、惩罚成本。把这些整理好再配上两三张关键曲线图一份能交差的仿真报告就成型了。6. 我踩过的坑和给你的排查清单6.1 不可行解先检查约束之间的隐性冲突遇到diagnostics.problem 1不可行时最常见的堆栈顺序是先检查储能SOC递推的时间系数再检查机组出力的上下限是否写反最后检查功率平衡等式是不是多了一个“”。我踩过最隐形的一个坑是在日内滚动MPC里预测时域内某个时段机组最小出力加上储能最大放电仍然不能满足负荷需求导致功率平衡约束硬约束不可行。这种问题在日前模型中不会出现因为日前计划可以提前安排机组启停但在MPC子问题里机组的启停在当前控制时刻已经固定无法短时间内再开一台机。解决办法是在日内MPC中允许负荷削减或增加弃负荷懋罚项给功率平衡约束加一个松软变量P_load(t) - P_curtail(t) P_shed(t) sum(P) P_pv P_dis - P_ch P_buyP_shed和P_curtail虽然是虚拟变量但加上高额惩罚后既保证模型永远可解又能通过它们定位“哪个时段缺电”。这是工程上很实用的兜底手段。6.2 求解时间爆炸整数变量规模失控怎么办如果你把日内MPC的滚动时域设成96个15分钟点再考虑4台机组启停那就有4×96384个0/1变量。虽然Gurobi还能扛但每15分钟跑一次大模型现场控制器很可能等不起。我的经验是三条路第一降低滚动时域比如预测时域只取8个点也就是未来2小时每15分钟滚动一次这样整数变量大幅减少第二对机组聚合把同型号同容量的机组合并成一个“聚合机组”用连续的线性化等效替代整数启停或者用启发式规则先定启停再做出力分配第三设置MIP gap上限比如mipgap0.02让求解器在2%最优性差以内就停下来现场的实时性比那2%的成本差重要得多。还有个隐藏问题求解器在MATLAB里反复调用时内存可能不释放。长时间仿真时要小心内存泄漏我一般每跑完一轮调用一次clear相关变量或者把模型构建和数据读取单独放函数里让局部变量自动清理。6.3 从仿真到工程落地的几个关键提醒仿真做完了如果要部署到实际微网监控系统里有几个工程细节不能忽略。第一数值尺度。目标函数里如果既有几万元的购电成本又有几角钱的启动成本求解器容易把小额项忽略掉。我建议把所有物理量都折算成标幺值比如成本以万元为单位功率以百千瓦为单位这样数值收敛性好很多。第二求解器授权。CPLEX和Gurobi的试用License都不能用于生产环境部署时要么买正版要么换开源的scip或highs。MATLAB自带的intlinprog在中小规模问题也能用但大规模场景下性能差距明显。做项目之前先确认好license不然排产到一半求解器罢工那种体验真是难以形容。第三和实时数据的接口。仿真里我们假设预测曲线已经拿到但实际系统里这需要和气象预报、负荷预测服务对接。我建议在MATLAB主程序里单独做一个数据接口函数把预测数据、实测数据和历史数据都抽象成标准输入结构体这样后期接入SCADA或物联网平台时只改一个接口函数就够了。最后说一个我自己的小习惯每次跑完一个算例我会把诊断信息、模型统计量变量数、约束数、非零元素数、求解时间、MIP gap、目标函数值全部存成一个日志文件。这是一个非常简单但收益极高的操作——因为微网项目里的问题往往不是一次能调好的三天后再回看某次结果时如果连当时用的参数都不记得调试就成了一场灾难。有了日志每次对比都有据可查也方便写报告时把仿真过程完整交代清楚。
返回列表