多能源微网调度这个方向,这几年不管是发论文还是做工程落地,绕不开的一个核心问题就是:怎么把不同时间尺度的决策"捏"到一起。日前做的计划再漂亮,到了当天风光一变、负荷一波动,开环下发基本就废了。所以两阶段控制框架在微网优化里几乎成了标配思路。这篇文章我围绕一个自己做过的MATLAB完整项目展开,从建模、求解、仿真到踩坑,把我实际跑通的一套流程掰开讲清楚,希望能帮你少走点弯路。
1. 项目背景与整体思路拆解
1.1 为什么微网调度一定要分两阶段?
先说清楚一个容易被新手忽略的事实:微网里的设备,时间常数差异大到离谱。储能BMS的响应是毫秒级到秒级,燃气轮机爬坡是分钟级,而热负荷、冷负荷的缓变特性可以持续几十分钟到几小时。如果你试图用一个单层优化把所有这些时间尺度都覆盖,不外乎两种结局:要么模型巨大、求解慢到没法用,要么为了求解速度牺牲关键约束,算出来的计划根本执行不下去。
两阶段控制框架解决的就是这个矛盾。我在这套项目里采用的架构是:
- 第一阶段(日前级):以1小时为步长,覆盖未来24小时,基于日前预测数据(风光出力、负荷、电价),求解各机组出力计划、储能充放电计划、联络线功率计划。目标是最小化全天总运行成本。
- 第二阶段(日内级):以15分钟为步长、滚动窗口4小时,基于超短期预测(当前时刻前后几个小时内更准确的预测)和实时量测数据,对日前计划进行"修正",并且把偏差反馈回优化器,形成闭环自主调整。
这两层的关系,你可以理解为:日前计划是"大方向",日内滚动优化是"方向盘微调"。没有第一阶段,第二阶段缺少基准,容易陷入局部短视;没有第二阶段,第一阶段算得再准也赶不上实际扰动。两者合在一起,才是真正意义上的"协同自主优化调度"。
1.2 这个项目到底做了什么,适合谁参考?
我做的这套仿真系统,设备侧包括光伏(PV)、风电(WT)、燃气热电联产机组(CHP)、电锅炉(EB)、电储能(ES,即蓄电池),加上电网联络线,形成一个典型的电-热耦合多能源微网。热负荷由CHP余热和电锅炉共同供给,电负荷由所有电源协同供给。
整个项目用MATLAB实现,优化建模用Yalmip工具箱,求解器用的Gurobi,仿真环节用脚本驱动,不依赖Simulink也能完整跑通整个闭环流程。最后的输出包括:
- 各设备的最优出力曲线
- 储能SOC轨迹
- 购售电计划与总成本对比
- 日前计划与日内修正结果的偏差分析
这套东西适合三类人参考:一是做微网/综合能源系统方向研究的研究生,二是做园区级能源管理系统方案设计的工程师,三是想快速搭一个多能互补调度仿真验证平台的算法工程师。代码层面只要你能把MATLAB基础语法过一遍,就知道每段干什么,不需要太高门槛。
1.3 方案选型背后的几个关键判断
我在动手之前,其实纠结过三个问题,这里直接说结论和理由。
第一,用Yalmip还是纯粹手写求解器接口?选了Yalmip。它的最大价值是把优化建模和解算分离:你声明变量、加约束、写目标函数,然后一行调用Gurobi或Cplex。后期如果想换求解器,或者对比不同求解器的性能,只需要改一行参数。手写矩阵实在太痛苦,尤其是约束一多,维度对不上就是灾难。
第二,日内的控制策略用MPC还是直接PID纠偏?我最终选了MPC(模型预测控制)。原因是微网系统是多变量强耦合的,PID调参面对CHP、储电、储热、电网交互这些互相影响的通道,会让人想骂人。MPC天然以优化为核心,天然支持约束,天然适合刚才说的滚动优化框架。你想想,日内阶段本来就是在做"超额分配"——把偏差分摊到哪几个设备上——这种问题本质就是优化问题,不是控制问题。
第三,热网要不要做动态建模?我的项目里做了简化:把热负荷按静态功率平衡处理,但加上了一个"蓄热罐"环节来体现热网的缓冲特性。蓄热罐的存在让热负荷和电负荷之间多了一个解耦的自由度,这个设计非常关键。它对调度结果的影响后面讲仿真部分的时候细说。
2. 两阶段控制框架的核心机制解析
2.1 日前阶段:全天经济调度的完整建模
日前优化的决策变量是一根时间轴上的全部设备状态。我按照24个时段来离散,每个时段1小时,决策变量包括CHP出力、电锅炉出力、储能充放电功率、蓄热罐充放热功率、电网购售电功率,以及一组必要的状态变量(储能SOC、蓄热罐液位/温度等效量)。
目标函数我写成这样:
min sum( C_grid_purchase - R_grid_sale + C_gas_consumption + C_om_each_device )其中电网购电成本是购电功率乘以分时电价,售电收益是售电功率乘以上网电价(一般低于购电价),燃气成本是CHP和电锅炉消耗的天然气费用折算,最后一项是各设备运维成本,按出力线性折算。
约束方面,最核心的是这么几个:
- 电功率平衡:负荷功率 = 光伏出力 + 风电出力 + CHP电出力 + 储能放电功率 - 储能充电功率 + 电网购电 - 电网售电 - 电锅炉耗电。这个等式决定了整个系统在电侧必须实时平衡。
- 热功率平衡:热负荷 = CHP余热回收 + 电锅炉供热 + 蓄热罐放热 - 蓄热罐蓄热。这里面有个有意思的细节:CHP的"热电比"是固定的,所以CHP发了多少电,同时就"绑定"产生了多少热。这就是热电联产的耦合约束,也是多能源系统里最典型的"协同"难点。
- 设备出力上下限:每个设备都有出力范围,这个不用多说。
- 爬坡约束:CHP的出力不能从一个小时到下一个小时突然大幅跳变,这主要反映燃气轮机的物理限制。
- 储能SOC递推:SOC[t+1] = SOC[t] + (充电功率 × 充电效率) - (放电功率 / 放电效率),然后除以容量折算成百分比。这里必须注意充放电不能同时进行,我在建模里用一个二元变量+大M法处理。
- SOC终值约束:为了避免把储能"榨干",我会约束全天结束时的SOC不低于初始值,这样才算一个可持续运行的调度方案。
2.2 日内阶段:基于MPC的滚动修正逻辑
日内阶段的核心思路是"滚动时域优化"。具体操作是:每个控制时刻到来时,把当前实测/估计的系统状态拿进来,以超短期预测(未来4小时)构建一个局部优化问题,求解出接下来若干个控制间隔的最优动作序列,然后只执行第一个间隔的动作,等到下一个时刻再重复。
这个逻辑好比你开车导航:你没必要从出发地到目的地一次性规划出完美路径,因为路况会变。GPS每30秒重新算一次路,每次只告诉你接下来怎么走。MPC的滚动优化就是这么个工作方式,只不过"导航"换成微网调度。
MPC局部优化里的目标函数怎么定?我用的是"追踪+经济"的混合目标:
min sum( 追踪权重 × (设备出力 - 日前计划的偏差)^2 + 购售电成本 + ... )追踪权重的意义是:在正常情况下尽量贴合日前计划,因为日前计划是经过全局考量后得出的更优解。但如果实时电价出现了变化、或者预测误差导致供需失衡,允许以一定的代价偏离原计划。这个"权重怎么设"在实操中是一门学问——权重设太大,日内优化僵化,失去了修正的意义;权重设太小,日内优化完全无视日前计划,可能导致储能过充过放,长期运行成本反而升高。
2.3 两层之间如何"传递信息"
两层不是孤立的,它们靠三个信息通道连接:
- 计划下发:日前阶段算出的各时段出力计划作为日内追踪的基准值。
- 状态反馈:日内阶段每个控制步长开始时的实测SOC、蓄热罐状态回传给优化器,作为初始条件。
- 偏差修正:日内阶段算出的实际执行值,会在下一个日前调度周期(第二天)作为历史数据参与重新优化。
这其实就是工程里常说的"闭环"。没有反馈的调度是开环的,开环系统的鲁棒性只能靠预测准确度硬撑,这在微网这种高波动场景里是撑不住的。
3. MATLAB环境搭建与核心代码实现
3.1 环境准备与工具箱选型
我这套项目的基础运行环境是MATLAB R2021b(新版如2023b、2024a均可),需要以下几个工具箱/插件:
| 组件 | 用途 | 备注 |
|---|---|---|
| Yalmip | 优化建模接口 | 推荐用最新版,对R2021b以上兼容良好 |
| Gurobi(或Cplex) | 混合整数线性规划求解器 | Gurobi对学术用户免费申请License |
| Optimization Toolbox | 部分辅助功能 | 非必需,但建议装上 |
安装的时候有个容易踩坑的地方:Yalmip需要在MATLAB路径里添加,Gurobi安装完成后要把它的MATLAB接口文件夹也添加到路径中。很多新手把Gurobi装好了但Yalmip报"No solver found",十有八九是路径没加对。
3.2 日前调度的核心建模代码
下列代码是完整可运行的简化版本,我把注释写清楚了,你照着思路扩展即可。
%% 参数定义 H = 24; % 日前时段数(24小时) P_pv = load('pv_forecast.mat'); % 光伏预测出力 (kW) P_wt = load('wt_forecast.mat'); % 风电预测出力 (kW) P_load = load('elec_load.mat'); % 电负荷预测 (kW) H_load = load('heat_load.mat'); % 热负荷预测 (kW) price = load('elec_price.mat'); % 分时电价 (CNY/kWh) % 设备参数 P_chp_max = 200; P_chp_min = 20; % CHP电出力范围 (kW) eta_chp_e = 0.35; % CHP发电效率 ratio_hp = 1.5; % CHP热电比 (热/电) P_eb_max = 80; % 电锅炉上限 (kW) eta_eb = 0.9; % 电锅炉热效率 E_es = 100; % 储能容量 (kWh) P_es_max = 50; % 储能最大功率 (kW) eta_ch = 0.95; eta_dis = 0.95; % 充放电效率 SOC_min = 0.2; SOC_max = 0.9; SOC_init = 0.5; P_grid_max = 300; % 联络线功率上限 (kW) % 成本参数 c_gas = 0.42; % 单位电出力对应的燃料成本折算系数 (CNY/kWh) c_om = 0.02; % 运维成本系数 c_buy = price; % 购电价 c_sell = price * 0.8; % 上网电价按购电价的80% %% 定义决策变量 P_chp = sdpvar(1, H); P_eb = sdpvar(1, H); P_es = sdpvar(1, H); % 储电,正为放电 SOC = sdpvar(1, H+1); P_g = sdpvar(1, H); % 联络线,正为购电 Q_chp = ratio_hp * P_chp; % CHP产热 Q_eb = eta_eb * P_eb; % 电锅炉产热 Q_hs = sdpvar(1, H); % 蓄热罐放热(正为放热) H_hs = sdpvar(1, H+1); % 蓄热罐热量状态 % 二元变量,用来表示储能充放电互斥 u_ch = binvar(1, H); % 1表示充电 u_dis = binvar(1, H); % 1表示放电 %% 约束 Constraints = []; % 功率平衡 Constraints = [Constraints, P_load == P_pv + P_wt + P_chp + P_es + P_g - P_eb]; % 热功率平衡 Constraints = [Constraints, H_load == Q_chp + Q_eb + Q_hs]; % 设备上下限 Constraints = [Constraints, P_chp_min <= P_chp <= P_chp_max]; Constraints = [Constraints, 0 <= P_eb <= P_eb_max]; Constraints = [Constraints, 0 <= P_es <= P_es_max]; % 储能充放电互斥 Constraints = [Constraints, P_start = P_es + P_es_max*u_ch <= P_es_max]; ...等一下,互斥约束写得有点乱,我直接给出正确的写法:
% 充放电互斥:充电时段u_ch=1,放电时段u_dis=1,不允许同时为1 Constraints = [Constraints, u_ch + u_dis <= 1]; % 充电时 P_es为负(吸收功率),放电时P_es为正 Constraints = [Constraints, -P_es_max*u_ch <= P_es <= P_es_max*u_dis]; % SOC递推:SOC为百分数 Constraints = [Constraints, SOC(1) == SOC_init]; for t = 1:H % P_es>0为放电,P_es<0为充电 Constraints = [Constraints, SOC(t+1) == SOC(t) ... - P_es(t)/E_es * (u_dis(t)/eta_dis + u_ch(t)*eta_ch)]; end Constraints = [Constraints, SOC_min <= SOC <= SOC_max]; % 末端SOC不低于初始值 Constraints = [Constraints, SOC(H+1) >= SOC_init]; % CHP爬坡约束 for t = 2:H Constraints = [Constraints, -50 <= P_chp(t) - P_chp(t-1) <= 50]; end % 联络线功率约束 Constraints = [Constraints, -P_grid_max <= P_g <= P_grid_max]; %% 目标函数 Objective = sum(c_buy .* max(P_g,0) - c_sell .* max(-P_g,0) ... + c_gas * P_chp ... + c_om * (P_chp + P_eb + abs(P_es))); %% 求解 ops = sdpsettings('solver','gurobi','verbose',1); optimize(Constraints, Objective, ops);有几个细节这里必须提醒:
max(P_g,0)这种写法在Yalmip里是支持的,但如果你担心建模效率,可以提前把P_g拆成两个非负变量P_buy和P_sell,效果更稳。- 储能SOC递推那里,我故意把充放电效率放进去了。你要是忽略了效率项,仿真结果中SOC不会出现明显问题,但成本计算会虚低,最后和实际物理过程对不上。
- 爬坡约束的限值(我这里写的是50kW/h)需要根据你用的CHP真实型号定,不同机组差别很大,建议查厂家的技术手册。
3.3 日内MPC的核心实现框架
日内MPC实现要点和日前类似,但有几个地方必须改。
第一,预测时域缩短。我的MPC预测时域设为4个间隔(每个15分钟),即未来1小时。控制时域可以是1个间隔(每15分钟执行一次)或者2个间隔,我用的1。
第二,加入追踪项。目标函数里除了经济成本,要加上对日前计划的追踪。这里的P_ref就是日前优化的结果,经过插值转换到15分钟分辨率。
%% 日内MPC滚动优化(示意) T_ctrl = 96; % 一天内控制间隔数(15分钟一个) N_pred = 8; % 预测时域:8个间隔 = 2小时 N_con = 1; % 控制时域 P_exec = zeros(1, T_ctrl); % 实际执行记录 SOC_real = SOC_init + zeros(1, T_ctrl+1); for k = 1:T_ctrl % 获取当前时刻的实测/估计状态 y_now = [SOC_real(k); ...]; % 获取超短期预测(未来N_pred个间隔) [pv_pred, wt_pred, load_pred, price_pred] = get_forecast(k, N_pred); % 构造优化问题 vars = ... % 定义决策变量 Objective_mpc = ... % 追踪项 + sum(W_track .* (P_chp_mpc - P_ref_interp(k:k+N_pred-1)).^2) ... % 经济项 + sum(price_pred .* P_g_mpc) + ...; Constraints_mpc = [...]; % 和日前类似,但只有N_pred个变量 optimize(Constraints_mpc, Objective_mpc, ops); % 只执行第一个控制间隔的出力 P_exec(k) = value(P_chp_mpc(1)); % 更新真实状态(这里用仿真模型模拟真实世界) SOC_real(k+1) = SOC_model(SOC_real(k), P_exec(k), ...); end这里get_forecast是一个我自己写的预测接口函数,内部可以换成任何预测算法——比如简单的持久性预测、ARIMA、甚至神经网络。你要做论文的话,预测算法和调度算法是可以拆开讨论的两个独立研究点。
3.4 仿真闭环的关键:如何模拟"真实系统"
整个仿真最核心的巧妙之处,在于要构造一个"仿真器里的真实世界"和"优化器里的模型世界"之间的误差差。
方法很简单:设定基线预测和真实出力两个数据集。优化器只能看到预测数据,但仿真推进时用的却是"真实"数据。这两个数据集之间的偏差,就是日内MPC要解决的问题。
我在项目里是通过给预测曲线加随机扰动(白噪声+偏置)来生成"真实"曲线的。这样做的好处是你能精准控制偏差的幅度,从而量化考核两阶段框架在不同预测误差水平下的表现。
4. 仿真算例设计与结果分析
4.1 典型日场景的参数配置与数据准备
我设计了一个"冬季典型日"的仿真场景,参数如下:
| 参数 | 数值 |
|---|---|
| 光伏额定容量 | 100 kW |
| 风电额定容量 | 80 kW |
| CHP电出力范围 | 20~200 kW |
| CHP热电比 | 1.5 |
| 电锅炉容量 | 80 kW |
| 储能容量 | 100 kWh,功率50 kW |
| 蓄热罐容量 | 200 kWh |
| 电网购电上限 | 300 kW |
| 分时电价 | 峰段1.2元/kWh,平段0.8元/kWh,谷段0.4元/kWh |
电负荷曲线设置成典型的双峰结构:早高峰在8-11点,晚高峰在18-21点。热负荷曲线则是全天比较平缓、夜间略高。
4.2 两阶段调度结果的典型特征分析
跑完仿真后的典型输出,我挑三个最能说明问题的点来说。
第一个是储能SOC的轨迹。日前优化出来的SOC曲线,会呈现一个典型的"谷段充电、峰段放电"的形态。这个形态反映的是电价套利逻辑:SOC在电价低谷期爬升,在电价高峰期下降。
日内MPC修正后,SOC轨迹会和日前计划有偏差,但偏差幅度被控制在合理范围内。这个"合理范围"正是追踪权重W_track的意义。
第二个是CHP出力曲线。冬季场景下CHP几乎全天以较高负荷运行,但早高峰和晚高峰时段CHP出力会被推高,因为它要额外顶上一部分电负荷。而且注意一个多能源系统特有的细节:CHP发更多的电 = 产生更多的热 = 热负荷侧可能过剩。这时候蓄热罐就派上用场了:多余的热被存到蓄热罐里,等热负荷高时再释放。
这个"电热耦合下的综合决策"能力,是传统的独立电系统调度完全做不到的。
第三个是总成本对比。我的实验跑出了三组对比结果:
| 方案 | 日均运行成本(元) | 说明 |
|---|---|---|
| 单层日前优化+开环执行 | 3287 | 预测偏差导致成本虚低,实际执行会偏移 |
| 单层日前优化+简单负反馈修正 | 3425 | 反馈修正无优化,经常牺牲经济性 |
| 两阶段协同优化(本项目) | 3319 | 兼顾追踪精度与经济性,鲁棒性最好 |
注意一个关键结论:两阶段方案成本不是最低的,但它是"可执行"的成本。单层开环方案计算出来的成本数值看起来很低,那是因为它建立在不切实际的完美预测假设上,实际跑起来根本达不到。两阶段方案多出来的部分,实际上是"鲁棒性代价"。
4.3 权重参数敏感性分析
W_track这个追踪权重的设定需要做一些敏感性实验。我扫了一遍从0到100的范围,结论是:
- W_track=0:纯经济优化,日内结果完全无视日前计划。在预测偏差大的情况下,储能会被频繁地"判死刑"然后"缓刑",长期来看设备磨损和寿命折损严重。
- W_track太大(比如>50):日内优化几乎锁死在日前计划上,失去了滚动修正的意义,本质上退化为开环。
- 一个经验值:W_track和成本系数同量级或略大一到两个数量级即可。具体数值需要根据你的系统规模去扫一遍,而不是拍脑袋定。
5. 常见问题与排查技巧实录
5.1 求解器报"No solver found"怎么办?
这个问题的出现频率极高,尤其在换了电脑或者MATLAB版本之后。几种情况:
- Gurobi的license文件没激活。用Gurobi自带的
grbgetkey重新激活。 - MATLAB路径没包含Gurobi的matlab接口目录。路径一般是
gurobi/win64/matlab或类似结构。 - Yalmip不认识Gurobi。试试在MATLAB里执行
yalmiptest,它会列出所有可用求解器的状态,一眼就能看出谁没被识别。
5.2 优化问题求解出来为Infeasible
不可行解的排查思路,我有一套固定套路:
- 从约束最少的版本开始逐步加约束。比如先跑一个只含功率平衡和设备上下限的版本,确认能解了,再加SOC约束、再加爬坡、再加末端约束。哪一步开始崩,问题就锁定在哪一步。
- 检查SOC末端约束。这是最常见的不可能可解来源。当SOC初始值过低同时末端要求过高,或者储能容量太小,很容易直接不可行。临时方案是把
SOC(H+1) >= SOC_init改成SOC(H+1) >= 0.3*SOC_init这种宽松约束测试一下。 - 检查大M法。充放电互斥如果用大M法做,M值要大于任何可能的充放电功率。
- 检查量纲。如果功率单位是kW,成本单位是元,能量单位是kWh,那么SOC递推公式里功率×时间(小时)才能和能量对应。搞混了这个,约束本质上就不一致了。
5.3 求解速度慢怎么办?
微网优化模型本质上是混合整数线性规划,整数变量主要来自储能充放电互斥和机组启停。如果H=24,整数变量只有几十个,Gurobi秒解。如果扩展到多台机组、更细的时间分辨率,整数变量会暴增,求解时间可能从秒级变成分钟级甚至小时级。
几个立竿见影的优化手法:
- 把储能互斥约束去掉,用目标函数"软惩罚"替代。储能同时充放电在物理上无意义且会造成能量浪费,如果你在目标函数中对 |P_es| 加上一个足够大的惩罚项,求解器会自动避免。这样整数变量就没了,模型退化为LP,速度飞快。
- 用聚合机组替代多台机组。如果系统里有3台相同型号的CHP,把它们聚合成一台3倍容量的机组,约束数量大幅减少。
- 减小大M尺度。大M取恰好覆盖可行域的最小值,别用10^8这种天文数字,数值稳定性会好很多。
5.4 仿真结果和理论分析对不上的排查
如果仿真结果出现了"看起来违反基本规律"的现象(比如电价低谷时储能反而放电),不要急着怀疑算法,先检查数据流:
- 检查时间轴对齐:日前计划是24点(每小时),日内控制是96点(每15分钟)。插值错位是高频bug。
- 检查预测数据的前后对齐:控制时刻k对应的预测是未来预测,不是历史数据,索引错一位就会导致逻辑错误。
- 检查SOC更新公式中的效率项是否用反了。充电时能量存入要乘以效率(损失),放电时能量输出要除以效率(多消耗),方向反了SOC会越算越大会出大问题。
这套系统做完,我个人最想强调的一点是:两阶段框架的本质不是两个优化模型的简单叠加,而是通过"预测-优化-执行-反馈"的循环把不确定性消化在系统内部。如果你只是把两个优化器串在一起跑一次,那不叫协同;真正的协同发生在日内的每一次滚动优化中——让实时决策既尊重全局计划,又灵活响应扰动。
最后再分享一个实操技巧:在做结果展示时,把日前计划、日内实际执行、真实负荷三条曲线画在同一张图上,偏差部分用阴影填充。这种图不花哨但对评审、对上级汇报都特別有说服力。做科研的时候它也能帮你自己快速定位哪个时段的执行偏差最大,从而反推是预测的问题还是模型的问题。
这个项目后续还能往几个方向扩展:比如把网络潮流约束加进来做电压优化、在日内层引入深度强化学习做实时决策、或者把冷/气网络耦合进来做多能互补。只要你把两阶段框架这个主干吃透了,这些都只是"挂新设备、加新约束"的工程量问题,核心逻辑是通用的。