昨天一个师弟找我调微电网调度代码,他运气不错,电话里我一眼就看出了问题:储能的SOC递推公式方向搞反了,结果优化出来的策略是“越到晚上越充电,白天全在放电”,完全和分时电价对着干。这种事我见了不止一次。微电网最优调度,说白了就是给出一组第二天的负荷预测、光伏风电出力预测、设备参数和分时电价,用数学优化算出一份从零点到二十四点的设备出力计划:柴油机什么时候开、发多少,储能什么时候充电什么时候放电,电不够了从主网购多少,光伏发多了要不要卖给电网,让一整天的总运行费用最低。这套东西在电力系统里叫日前经济调度,是微电网能量管理系统(EMS)的核心模块。主流的做法就是MATLAB配YALMIP建模,再用CPLEX求解器去解。这篇文章我把完整思路、建模步骤、代码骨架和调试中经常踩的坑一次性讲清楚,适合刚接触微电网优化调度的研究生和工程师。
1. 调度问题到底在优化什么
1.1 目标函数:一天的钱怎么算
既然目标是“一天运行费用最小”,第一步就是把费用拆明白。拿一个典型微电网来说,日运行费用通常由四块构成。
第一块是柴油发电机(或者微型燃气轮机)的燃料费用。燃料费用一般和出力有关,工程上常用二次函数近似:
F_dg(t) = aP_dg(t)^2 + bP_dg(t) + c*u_dg(t)
其中u_dg(t)是开机状态,取0或1,c可以理解为空载油耗。注意,如果直接把这个二次函数丢给CPLEX,问题就从MILP变成了MIQP(混合整数二次规划),不是不能解,但明显会更慢。实际工程中更顺手的方法是分段线性化,或者像我后面例程里那样,直接简化为线性:燃料费用 = 斜率×出力 + 固定空载费用。单台小机组误差在可接受范围,求解速度却快很多。
第二块是向大电网购电的费用。某个时段如果微电网自己发的电不够,就要从主网购电,费用是购电量乘以该时段购电价。第三块是售电收益,光伏出力大、负荷又小的时候,多余电能卖给主网,收益可以抵扣成本。第四块是储能损耗折旧,虽然单个时段看着很小,但乘上365天就值得考虑。
日运行总费用写成一个离散和,每个时段算一次费用再加起来。决策变量是各时段出力、储能充放电功率、购售电功率。优化器要做的,就是在所有可行的出力组合里,找到让总费用最小的那一个。
1.2 微电网这个“家”都有哪些成员
先盘点微电网里的常见设备,建模的时候它们各有脾气。
光伏和风电是跟随天气走的,出力有预测值,但调度时允许“弃光”和“弃风”,也就是说实际投入使用的出力可以小于预测值。这给了优化器一定的灵活性,比如中午光伏大发、电价又低、储能也满了,宁可弃掉一部分也划算。
柴油发电机是最灵活的主动电源,想开就开、想停就停,但有最小技术出力限制,比如一台50kW的机组,你不能让它只发10kW。它还有爬坡约束,相邻两个时段之间出力变化不能太猛,这是机械本身的物理限制。你可以类比成开车,油门不能从零直接踩到底。
储能是全场的调节能手,但建模最麻烦。它的电量状态(SOC)是一个递推过程:这一小时结束时的电量等于上一个小时结束的电量加上充电量、减去放电量,还要折算充放电效率。充放电不能同时进行,这个约束必须显式写出来,否则优化器会钻空子:同一小时又充又放,白白制造损耗,结果还自以为很聪明。
和主电网的交互也要建模。联络线功率有上限,而且购电和售电不能同在一个时段发生,这部分常用0-1变量来控制。
这就是一个典型微电网调度模型的全部要素。剩下的事情,就是把这些要素用数学公式写清楚,再交给求解器。
2. 为什么是MATLAB+YALMIP+CPLEX这个组合
2.1 YALMIP是干什么的
刚接触这套技术栈的人最容易问的问题是:我直接写CPLEX的MATLAB接口不好吗,为什么中间还夹着一个YALMIP?
CPLEX确实提供MATLAB API,但它偏底层。如果要定义一大批变量、约束、目标函数,代码会很冗长。YALMIP是一个建模工具箱,来自瑞典学者Lofberg的开源项目。它的核心价值在于让你用接近数学公式的方式写优化模型:sdpvar定义连续变量,binvar定义0-1变量,约束直接写等式或不等式,最后调用optimize求解。YALMIP会自动把模型翻译成求解器需要的标准形式,调用底层求解器计算。
用YALMIP写微电网调度,最大的好处是代码和论文里的公式几乎一一对应,调试的时候能对照着看。另外你随时可以换求解器,同一个模型,把solver从cplex改成gurobi或cbc,几乎不用动其他代码。这意味着你不被某个商业求解器绑架。
2.2 CPLEX处理这类问题的优势
微电网调度问题在数学上是一个混合整数线性规划(MILP)。为什么会有“整数”?因为储能充放电状态的0-1、机组启停的0-1、网购电售电状态的0-1,都是整数变量。MILP的求解难度比单纯线性规划(LP)高得多,需要分支定界、割平面这些算法。
CPLEX是IBM的商业求解器,在MILP领域打磨了几十年,分支定界策略、启发式算法、预求解过程都很成熟。同样一个大规模调度问题,CPLEX通常比开源求解器CBC快很多,尤其当整数变量上百个的时候。学术版还可以免费申请,用学校邮箱下载IBM ILOG CPLEX Optimization Studio,对学生和科研人员非常友好。
纯用MATLAB自带linprog只支持连续变量,遇到储能互斥、机组启停这类0-1变量就无能为力了。所以这套组合的好处很清晰:YALMIP负责让建模“像人话”,CPLEX负责把“人话”变成高效计算,MATLAB负责写脚本、做数据处理、画结果图。
2.3 安装配置里最容易出问题的几个点
如果你是新机器,按这个顺序来基本不会出错。
先装MATLAB,版本不要太老,2020b以上比较省心。再装CPLEX,安装路径里不要有中文。重点来了:装完CPLEX之后,要在MATLAB里把CPLEX的MATLAB接口目录加到路径里。CPLEX安装目录下通常会有一个cplex/matlab目录,在MATLAB里运行:
addpath('D:\Program Files\IBM\ILOG\CPLEX_Studio221\cplex\matlab'); savepath;不同版本路径不一样,但目标明确,就是要让MATLAB能找到cplexlink这个接口文件。然后是YALMIP,从GitHub下载或者官网下载,解压后把整个文件夹加到MATLAB路径。
配置完怎么验证?运行YALMIP自带检测命令:
yalmiptest输出里能看到CPLEX是否被识别,状态是“ok”那就稳了。如果yalmiptest里CPLEX那一行显示Not found,先检查路径,再检查MATLAB是不是以管理员权限运行的。
有一条实际经验:如果你同时装了多个版本的CPLEX,MATLAB会加载路径里的那一个。这时候在sdpsettings里显式指定solver为cplex就没问题,但如果路径里有旧版接口,YALMIP可能报版本不匹配,常见错误是“Unrecognized function or variable 'cplexlink'”。解决办法是清理路径,只保留一个CPLEX接口目录。
3. 从数据到数学模型的完整建模流程
3.1 第一步:准备一套站得住脚的数据
调度模型不是凭空算的,输入数据必须齐全。一个典型的日前调度,需要以下几类数据。
负荷预测是24个小时的序列,单位kW。光伏和风电出力预测同样按小时给。这些数据来源可以是实际电网运行记录,也可以根据典型日曲线生成。如果你手头没有实际数据,一个合理的办法是参考论文里用的典型日曲线,或者按规律自己造:负荷在早晚出现两个高峰,光伏在中午最高、夜间为0,风电夜里略大。
分时电价也很关键。很多地方实行峰谷分时电价,购买电价在不同时段不一样。比如工业用户尖峰时段1.2元/kWh,平时段0.8元/kWh,谷时段0.4元/kWh。售电价通常按购电价的八折到九折算,因为电网要赚差价。
设备参数方面:柴油机有最大最小出力、爬坡速率、燃料成本系数;储能系统有容量、最大充放电功率、初始SOC、SOC上下限、充放电效率;联络线有最大交换功率。这些参数看起来琐碎,但任何一个填错,优化结果都会走样。我自己吃过一次亏是单位搞混,功率用了kW,能量用了kWh,时间步长又是1小时,看起来数值一样,实际上这里没有单位陷阱,真正危险的是时间步长改成15分钟之后,能量单位要乘以0.25,忘了换算的话SOC递推直接爆掉。
有个小技巧:调试阶段所有数据都用简单的整数,一眼能看出结果是否符合直觉。比如负荷恒定100kW,光伏恒定0,柴油机最小出力50kW,储能容量200kWh,电价峰谷分明,最优策略应当是谷段充电、峰段放电,柴油机在电价高峰顶上去。如果算出来不是这个规律,说明模型里肯定有错。
3.2 第二步:选择决策变量
决策变量是优化器可以自由调整的量。在微电网调度里,典型的决策变量包括:
- 柴油机每小时出力P_dg(t),以及机组状态u_dg(t)
- 储能每小时充电功率P_ch(t)、放电功率P_dis(t),以及互斥状态b_ch(t)、b_dis(t)
- 储能SOC能量E_SOC(t)
- 每小时从电网购电P_buy(t)、售电P_sell(t)及交互状态u_net(t)
- 光伏实际使用出力P_pv_use(t)、风电实际P_wt_use(t)
在YALMIP里,连续变量用sdpvar,0-1变量用binvar。为了方便,直接定义成行向量,时间维度在后面用循环展开。
关于SOC为什么要定义成T+1个点而不是T个:因为递推公式里,要从E_SOC(1)开始,每一步算出E_SOC(t+1),最后需要T+1个状态点,其中E_SOC(1)是已知初始值,剩下的才是变量。这个细节非常容易疏忽。
3.3 第三步:把约束写成数学式子
约束条件按照功能分四组。
功率平衡约束是全场硬约束,每个时段都必须满足:
P_pv_use(t) + P_wt_use(t) + P_dg(t) + P_dis(t) + P_buy(t) = P_load(t) + P_ch(t) + P_sell(t)
等式左边是电源侧加流入方向,右边是负荷加充电和售电。为什么必须是等号?因为微电网和主网连接处的功率失配会导致频率波动,调度计划必须保证实时供需平衡。如果模型里写不等号,优化器很可能会利用这个“宽松度”少出力,结果不可用。
柴油机约束:出力要在最小技术出力和最大出力之间,只有开机状态才能出力:
P_dg_minu_dg(t) <= P_dg(t) <= P_dg_maxu_dg(t)
相邻时段还有爬坡约束:
-Ramp_down <= P_dg(t+1) - P_dg(t) <= Ramp_up
储能约束是重头戏。充放电功率各有上下限,并且受互斥状态控制:
0 <= P_ch(t) <= P_ch_max * b_ch(t) 0 <= P_dis(t) <= P_dis_max * b_dis(t) b_ch(t) + b_dis(t) <= 1
SOC递推是等号约束:
E_SOC(t+1) = E_SOC(t) + P_ch(t)*eta_ch - P_dis(t)/eta_dis
注意充电效率乘以充电量、放电效率除以放电量,两者不对称,这是不能省的细节。SOC整体还要在上下限之间:
SOC_min <= E_SOC(t) <= SOC_max
和主网的交互约束:购电和售电不能同时发生,且分别受联络线功率上限约束:
0 <= P_buy(t) <= P_net_max * u_net(t) 0 <= P_sell(t) <= P_net_max * (1 - u_net(t))
这组约束用u_net(t)=1表示购电,u_net(t)=0表示售电,两个条件互相排斥,非常经典。
3.4 第四步:明确求解目标并调用CPLEX
目标函数和约束都定义好之后,在YALMIP里用optimize(Constraints, Objective, ops)一行调用,指定solver为cplex。
ops = sdpsettings('solver', 'cplex', 'verbose', 2); ops.cplex.mip.tolerances.mipgap = 0.01; ops.cplex.mip.limits.timelimit = 120; sol = optimize(Constraints, Objective, ops);mipgap表示允许的整数最优间隙,1%对工程调度完全够用,设成0也不是不行,只是会明显增加求解时间。120秒是时间上限,防止凌晨预测数据变化导致后台优化卡死。verbose=2让你能实时看到分支定界进度,排查问题很有用。
求解结束后还要检查状态:
if sol.problem == 0 disp('求解成功'); P_dg_opt = value(P_dg); else disp(sol.info); endsol.problem == 0代表求解成功,其他返回值对应不同错误,后面排查章节会详细讲。
4. 一个能直接跑的完整例程
4.1 主程序骨架
下面给出一个最小可用例程。模型规模不大,24个时段,1台柴油机,1套储能,1条联络线。我故意把数据写得简单,方便你代入感性地理解。
%% 微电网日前经济调度 完整例程 % 使用 YALMIP + CPLEX % 时间分辨率:1小时;调度周期:24小时 clear; clc; close all; %% 1. 数据定义 T = 24; dt = 1; % 单位小时 % 负荷预测 (kW) P_load = [120 115 110 105 100 95 98 110 135 150 165 175 180 170 160 150 ... 155 160 170 165 155 145 135 125]; % 光伏预测 (kW) 白天有出力 P_pv = [0 0 0 0 0 0 10 45 90 130 160 175 170 150 120 80 40 5 0 0 0 0 0 0]; % 风电预测 (kW) 夜间略大 P_wt = [30 32 35 38 36 34 32 30 28 26 25 24 26 28 30 32 35 33 30 28 26 25 24 23]; % 分时电价 (元/kWh) price_buy = [0.4*ones(1,8), 0.8*ones(1,2), 1.2*ones(1,3), 0.8*ones(1,7), ... 1.2*ones(1,2), 0.8*ones(1,2)]; % 谷-平-尖-平-尖-平 price_sell = 0.85 * price_buy; %% 2. 设备参数 P_dg_max = 200; P_dg_min = 50; % 柴油机出力上下限 kW Ramp_dg = 60; % 爬坡限制 kW/h a_fuel = 0.55; c_fuel = 40; % 燃料成本系数:a*P + c*u B_rated = 400; % 储能额定容量 kWh S0 = 0.2 * B_rated; % 初始SOC能量 kWh S_min = 0.1 * B_rated; % SOC下限 S_max = 0.9 * B_rated; % SOC上限 P_b_max = 100; % 储能最大充放电功率 kW eta_ch = 0.95; eta_dis = 0.95; % 充放电效率 P_net_max = 500; % 联络线最大交换功率 kW %% 3. 决策变量 P_dg = sdpvar(1, T); u_dg = binvar(1, T); % 柴油机启停 P_buy = sdpvar(1, T); P_sell = sdpvar(1, T); u_net = binvar(1, T); % 1:购电 0:售电 P_ch = sdpvar(1, T); P_dis = sdpvar(1, T); b_ch = binvar(1, T); b_dis = binvar(1, T); E_SOC = sdpvar(1, T+1); % 注意T+1 P_pv_use = sdpvar(1, T); P_wt_use = sdpvar(1, T); %% 4. 约束条件 Constraints = []; % 功率平衡 Constraints = [Constraints, P_pv_use + P_wt_use + P_dg + P_dis + P_buy == ... P_load + P_ch + P_sell]; % 光伏和风电:可用量约束 Constraints = [Constraints, 0 <= P_pv_use <= P_pv]; Constraints = [Constraints, 0 <= P_wt_use <= P_wt]; % 柴油机 Constraints = [Constraints, P_dg_min*u_dg <= P_dg <= P_dg_max*u_dg]; for t = 1:T-1 Constraints = [Constraints, -Ramp_dg <= P_dg(t+1) - P_dg(t) <= Ramp_dg]; end % 储能充放电功率及互斥 Constraints = [Constraints, 0 <= P_ch <= P_b_max*b_ch]; Constraints = [Constraints, 0 <= P_dis <= P_b_max*b_dis]; Constraints = [Constraints, b_ch + b_dis <= 1]; % SOC递推 Constraints = [Constraints, E_SOC(1) == S0]; for t = 1:T Constraints = [Constraints, E_SOC(t+1) == E_SOC(t) + P_ch(t)*eta_ch - P_dis(t)/eta_dis]; end Constraints = [Constraints, S_min <= E_SOC <= S_max]; % 与主网交互 Constraints = [Constraints, 0 <= P_buy <= P_net_max*u_net]; Constraints = [Constraints, 0 <= P_sell <= P_net_max*(1-u_net)]; %% 5. 目标函数 Cost_fuel = sum(a_fuel*P_dg + c_fuel*u_dg); Cost_buy = sum(price_buy .* P_buy); Income_sell = sum(price_sell .* P_sell); Cost_battery = 0.01 * sum(P_ch + P_dis); % 电池损耗折算 Objective = Cost_fuel + Cost_buy - Income_sell + Cost_battery; %% 6. 求解 ops = sdpsettings('solver', 'cplex', 'verbose', 2); ops.cplex.mip.tolerances.mipgap = 0.01; ops.cplex.mip.limits.timelimit = 120; sol = optimize(Constraints, Objective, ops); if sol.problem == 0 P_dg_opt = value(P_dg); P_buy_opt = value(P_buy); P_sell_opt = value(P_sell); P_ch_opt = value(P_ch); P_dis_opt = value(P_dis); E_SOC_opt = value(E_SOC); P_pv_use_opt = value(P_pv_use); P_wt_use_opt = value(P_wt_use); disp(['最小日运行费用: ', num2str(value(Objective))]); else disp(sol.info); end %% 7. 结果可视化 figure; hold on; t = 1:T; bar(t, P_load', 'k', 'FaceAlpha', 0.2); bar(t, P_pv_use_opt', 'y'); bar(t, P_wt_use_opt', 'c'); bar(t, P_dg_opt', 'r'); bar(t, P_dis_opt', 'g'); bar(t, P_buy_opt', 'b'); legend('负荷','光伏实际','风电实际','柴油机','储能放电','网购电'); xlabel('时段(h)'); ylabel('功率(kW)'); title('微电网调度出力结果'); grid on;4.2 代码里几个关键点的详细说明
先看E_SOC = sdpvar(1, T+1)这一行。我见过程序写法是定义成T个变量,结果循环到E_SOC(t+1)时直接越界报错。定义成T+1个,让E_SOC(1)存初始值,E_SOC(2)到E_SOC(25)分别对应第1到24小时结束时的电量,这样递推关系写起来顺且不丢状态。
再看充放电互斥约束。我用了b_ch + b_dis <= 1,这里其实是“弱互斥”:允许两个都为0,也就是储能既不充也不放,这符合物理事实。但是要小心边界情况:如果优化器发现“充电再放电”能创造收益,它会想方设法让两个二进制都为1,所以这个约束绝不能省。另一种更严谨的写法是用implies(b_ch, P_dis==0),逻辑上等价,但实际调试中上面的写法更直观,也不会产生数值困难。
目标函数里Cost_battery = 0.01 * sum(P_ch + P_dis)是一个可选的折旧成本。它的作用不只是算钱,更重要的是抑制储能无意义的反复充放,让优化结果更符合电池寿命管理。实际项目可以根据电芯成本和循环寿命标定出每kWh损耗费用,一般几分钱,但不可缺少。
还有个细节:售电价price_sell = 0.85*price_buy,这会让优化器尽量少“低买高卖”,因为购售存在价差,套利空间被压缩,结果更符合真实电网规则。如果把售电价设成和购电价一样,优化器可能会在谷段大量购电、再同时段高价卖出形成无风险套利,这在模型上虽然可行,但在真实工程中电网侧不会允许。
4.3 结果怎么判断对不对
跑通程序之后,先别急着收工。把结果画出来,或者把关键数字打出来,按这几条规律肉眼检查。
储能SOC应当符合峰谷套利规律:谷时段充电,SOC上升;峰时段放电,SOC下降;一天结束时SOC不一定要回到初始值,但一般会落在允许范围内。柴油机出力和电价高相关:高峰时段负荷大、电价高,柴油机大概率满发或接近满发;低负荷夜间的谷段,柴油机可能直接停机。光伏和风电在模型里是“用满优先”,因为边际成本为零,只有在储能满、负荷小、售电又不划算的极端情况下才会弃光弃风。
我在调试时有一个习惯:先跑一个极端场景,比如把储能容量设为零,看柴油机和购电能不能撑着负荷;再把柴油机关掉重跑,看储能加新能源能不能撑住。这两个极端场景能暴露大部分约束错误。
5. 常见问题与排查技巧实录
5.1 求解器报错速查表
你大概率会遇到下面这几个问题,按表格里的思路排查,效率会高很多。
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
提示No suitable solver | YALMIP没找到CPLEX | 检查CPLEX的MATLAB路径;运行yalmiptest确认 |
提示Infeasible problem | 约束条件互相矛盾 | 逐段注释约束重跑,找到冲突源;检查功率平衡方向 |
| 结果全是0或NaN | 变量维度不对;数据没传进去 | 检查sdpvar维度;用size()打印确认 |
| 求解时间过长 | 整数变量太多,或目标为二次 | 分段线性化;设置mipgap和时限;减少不必要的0-1变量 |
| 结果不符合直觉 | SOC递推方向错;效率方向反 | 查看SOC曲线走势;检查充放电流向 |
5.2 Infeasible,也就是“模型不可行”怎么查
这是新手最容易崩溃的情况。所谓不可行,就是约束集合没有任何解满足。排查思路最关键的一条:不要盯着整个模型看,要“拆”。
我会先把储能相关约束注释掉,只保留功率平衡和柴油机约束,看还有没有可行解。如果可行,问题大概率在储能;如果仍不可行,再检查功率平衡或者柴油机约束。这个二分排查法在模型稍微复杂一点后非常有用。
另一个高频原因是爬坡约束过紧。比如柴油机最小出力50kW,上一时段停机(P=0),下一时段要求出力100kW,爬坡上限60kW,那么从0直接跳到100就违反了爬坡约束。正确的做法是给爬坡约束也乘以状态变量,写成更精细的形式,不过简单例程里可以先放宽爬坡限值跑通,再逐步收紧。
还有一次我遇到的不可行是因为联络线功率上限设成了200kW,但某个时段的负荷加充电需求是600kW,光伏风电又弱,柴油机最大200kW,无论怎么组合都不够。这种问题往往是数据自相矛盾,检查数据比检查模型更快。
5.3 求解慢的真正原因和优化手段
微电网日调度模型不算大,24个时段一般几秒到几十秒就能解完。如果发现慢到几分钟还不出结果,通常有两个原因。
一是目标函数里有二次项。CPLEX对MIQP不是不能处理,但分支定界的效率会下降一个档次。解决思路是把二次燃料成本线性化。常用的方法是分段线性逼近:把出力范围切成几段,每一段用一个线性函数近似,用SOS2约束或者直接加0-1变量选择段。对工程调度来说,三段线性化已经足够精确。
二是整数变量过多。比如你给每台柴油机、储能、联络线都加了0-1变量,还加了启动变量、停机变量,24时段乘以5个机组就是120个0-1变量,复杂度指数级上涨。优化手段包括:把同一机组的启停和启动变量合并;用mipgap放宽最优性要求,1%的间隙很多时候够了;设时间上限,让求解器在限定时间内给出一个可行次优解。实际项目里最不该缺的就是这个时间上限,否则某天数据异常,后台优化进程一直占着CPU,整个EMS都被拖垮。
5.4 电池建模里那些“看起来对但实际错”的细节
铺开来总结,储能建模的坑集中在三个地方。
第一个坑是充放电方向。很多初学者把功率平衡写成P_buy + P_dg + P_pv = P_load + P_ch + P_dis,把放电也当成了负荷,结果储能放电越多,系统“缺电”越严重,优化器自然选择永远不放电。这里有一个万年不变的检查方法:看结果里SOC是不是会下降,SOC永远不掉,放电功率那一路十有八九写反了。
第二个坑是效率的方向。充电效率应该乘在充电功率上,放电效率应该除在放电功率上。有人统一乘效率,结果用充电效率去套放电过程,储能每放一次就“凭空多出”一些能量,虽然数值差别不大,但会导致调度结果轻轻松松赚钱,看起来很美,实际跑不下去。
第三个坑是SOC的边界约束忘了加。如果只约束递推,不约束SOC上下限,优化器可能把SOC算成负数,储能变成无底洞。别觉得这是低级错误,我见过不止一个程序因为少了S_min <= E_SOC <= S_max,仿真结果里电费为负,还兴奋了半天,以为是优化威力大。
6. 从简单例程到实际工程的扩展思路
6.1 把单日调度改成滚动优化
上面的例程是静态日前调度,一次性算好24小时计划。但真实微电网的负荷和新能源出力预测不可能完全准,所以实际EMS里更多采用滚动优化的方式。
典型做法是:每隔15分钟或1小时,用最新实测数据更新预测,重新求解一次未来若干小时的调度问题,只执行第一个时段的指令,到了下一个周期再滚动更新。这叫模型预测控制(MPC)思路。实现上不需要改太多代码,把数据结构改成滑动窗口、把初始SOC改成当前实测值就够了。
我建议刚上手的同学先把日前静态跑通,再改成滚动,两版代码对比着看,对“约束如何随时间平移”会理解得很深。
6.2 加入备用约束和不确定性
经济调度如果只追求费用最小,结果可能在突发情况下“太脆”。实际工程会在约束里加旋转备用:每个时段留出一定比例的备用容量,比如负荷的5%加上光伏出力的10%,确保一台机组跳机或云层遮住光伏时还能补上。
从模型上看就是增加不等式约束:所有可用机组最大出力之和 >= 负荷 + 备用需求。这种做法代价不大,但对系统安全性的提升非常明显。
再进一步,如果你要严格考虑光伏、负荷预测误差,问题就从确定性优化变成随机优化或鲁棒优化。常见框架是场景法:抽一批典型随机场景,每个场景对应一组平衡约束,目标是最小化期望费用。这个扩展会让模型规模成倍增加,但YALMIP仍然能表达,只是求解时间要关注。
6.3 我对这套技术栈的一点个人体会
调到今天,我的感受是:微电网调度模型本身并不复杂,复杂的是数据和工程细节。你花在排查SOC方向错误、单位换算、电价时段划分上的时间,可能比写代码本身还多。
最后再分享一个我自己的习惯:每跑出一个合理结果,会把这组数据存成baseline,后续改模型或改参数,都把新结果和baseline对比,看差异是否符合直觉。跑多了你会发现,优化问题“对不对”比“快不快”重要得多。同样的模型,拉长到96个时段、加入多台机组之后,才真正考验你对YALMIP的熟练程度和CPLEX参数调优的功底。先把这篇例程吃透,再往上走,你会顺利很多。