做微电网调度的人,八成都有过这样的经历:前一天的优化方案算得漂漂亮亮,第二天早上光伏预测一更新,整条调度曲线直接作废。我最早做微电网EMS的日前调度时,用的是最经典的确定性优化,目标函数里风光出力取预测值、负荷取预测值、电价取预测值,求解器一分钟就交卷,看起来天衣无缝。结果一到实际运行,阴天、多云、跳负荷、电价波动,任何一个环节偏离预测,所谓的“最优方案”就变成了高额购电的单程票。
这篇内容不打算从抽象定义讲起,而是一线工程师视角的实战笔记。你会看到一个微电网日前鲁棒调度模型是怎么从确定性版本一步步改出来的,包括不确定性怎么建模、预算参数怎么调、求解器怎么写、以及我实际踩过的几个坑。适合正在做微电网EMS、储能系统调度的算法工程师,也适合刚接触鲁棒优化、想把它从论文搬到工程里的研究者。
1. 微电网里的不确定性,比想象中要狠得多
1.1 风光出力:最不讲信用的一环
光伏和风电是微电网里最典型的“随机电源”,也是我见过最让调度模型头疼的输入参数。天气预报说晴天,你按中午120kW出力建模,结果空中飘过一片云,十分钟内出力能掉到30kW。风电更夸张,风机出力随风速变化,而风速本身在时间尺度上就是完全随机的过程,风向一变,整排风机的出力曲线跟你预测的基本是两条线。
这里有个容易被低估的地方:预测误差不是均匀分布的,它往往集中在高出力时段。光伏在中午达到峰值时,云层遮挡造成的绝对偏差最大,可能动辄几十千瓦。你如果按固定比例(比如±10%)建模,实际误差在峰值时段可能是±30%以上。反过来,夜间光伏出力接近零,即使相对误差很大,绝对偏差也小得可忽略。
所以我在做微电网鲁棒调度时,对风光出力的不确定性做了两件事:第一,按时段统计历史预测误差,而不是图省事用一个全局百分比;第二,把误差的分布形态也记录下来,因为后面定不确定集边界、选分位数的时候,这个历史分布就是依据。
1.2 负荷和电价:误差也会要命
很多人觉得负荷预测比风光容易得多,毕竟用户用电有比较强的规律性。这话在日常场景下大体成立,但一旦碰到极端天气、节假日或者某个大用户临时增加产线,负荷预测曲线也能给你来一次“惊喜”。微电网的特点是体量小,整体负荷可能就一两百千瓦,一个大设备启停就是十几千瓦,相对误差轻松超过10%。这类偏差在功率平衡约束里同样会传导成购电计划错误。
电价的不确定性在电力市场环境下更复杂。现货电价受全网供需、新能源出力、燃料价格多方影响,波动幅度远高于微电网内部的负荷波动。如果你的微电网允许向主网购电、售电,那电价预测误差会直接影响目标函数里的成本项,甚至让“该充该放”的决策整体反转。我遇到过电价预测偏差导致储能策略完全反了的情况:模型以为晚上是低谷电价,安排了充电,实际现货价格却因为风电出力骤减而飙升,结果一边充电一边承担高价电费。
1.3 确定性优化的“玻璃心”本质
确定性优化把所有输入参数当成已知常数,求解出的是一组“在预测完全准确的前提下最优”的方案。问题在于,预测不可能完全准确,而微电网又不像大电网有那么大的惯性可以兜底。
打个比方,确定性优化就像一个只按天气预报出门的人,预报说晴天就绝不带伞。天气预报准的时候确实轻松,但一旦预报错,淋雨的代价远超“带伞”的那点麻烦。更麻烦的是,微电网里的储能容量有限,燃气机爬坡有速率限制,联络线功率也有上限。当预测偏差出现时,这些约束会让你根本来不及补救,只能被迫用最贵的现货电来填缺口。
从我自己的项目经验看,确定性优化方案在实际运行中的“最优”往往只能维持几个时段。只要第一个时段实际和预测出现偏差,后面整个调度序列的参考价值就大打折扣,这就是我觉得鲁棒优化值得在微电网场景里认真对待的根本原因。
2. 鲁棒优化的核心逻辑:把最坏情况请进模型里
2.1 不确定集:给不确定参数画个结界
鲁棒优化和传统优化的根本区别,是它不再把不确定参数当成一个点,而是当成一个集合。这个集合叫“不确定集”,用来刻画“实际值可能落在哪个范围内”。
最常见也最容易理解的是盒式不确定集,也叫箱式不确定集。对光伏出力P_pv(t),如果预测值是P_pv^f(t),预测误差上界是Δpv(t),那不确定集就是:
P_pv(t) ∈ [P_pv^f(t) − Δpv(t), P_pv^f(t) + Δpv(t)]
也就是说,模型不再只盯着预测曲线那个“点”,而是把整条可能的出力区间都纳入考虑。负荷、电价都可以用同样的方式建模。
但这里立刻会出现一个问题:如果每个时段都允许不确定参数取到区间端点,而且所有时段同时取端值,那模型会变得极度保守。比如光伏每个时段都取下界、负荷每个时段都取上界,等于给系统模拟了一个“全天无风无光且用电爆表”的极端场景,这样的方案成本会高得离谱,甚至可能出现不可行解。
2.2 预算约束:给最坏情况上个限流阀
为了处理上面说的“过度保守”问题,Soyster之后的一批学者发展出了预算鲁棒优化,最常用的是Bertsimas和Sim提出的方法。它的核心思想是:最坏情况可以发生,但不可能所有时段同时发生。你可以用一个预算参数Γ来限制“偏离预测”的时段总量。
用我自己的项目来说,光伏和负荷一起迭代出一个预算参数Γ,含义大致是“全天24个时段里,我最多允许几个时段同时处于最坏情况”。Γ=0代表完全信任预测,退化成确定性优化;Γ=24代表每个时段都提防最坏情况,就是那种极端的盒式鲁棒。实际操作时,Γ通常取一个中间值,比如6到10,代表一天里只为一部分时段的最坏情况做准备。
这样一套设计在工程上是说得通的,因为光照变化、负荷突变确实有一定的“同时性”,但不会像纯盒式建模那样假设全天每个小时都灾难。预算约束相当于给灾难场景上了个限流阀,让模型在“太乐观”和“太悲观”之间找到落点。
2.3 两层结构为什么适合微电网
鲁棒优化的建模思路天然带着“博弈”味道。外层是调度员的决策:今天每个时刻机组发多少电、储能充放多少、买多少电,这是你现在就要定下来的;内层是大自然的选择:风光出力、负荷、电价这些不确定参数在最坏的情况下会取什么值。优化目标就是在“大自然选最坏情况”的前提下,让外层决策的总成本最低。
用微电网的术语说,外层变量是“here-and-now”决策,比如燃气机的开停机状态、日前购电计划、储能的充放电策略;内层变量是“wait-and-see”的扰动结果,比如实际光照导致的出力偏差。鲁棒优化把这套博弈变成了一个min-max问题:先假定内层取最坏值,再找外层的最优解。
这种两层结构对微电网特别适合,因为微电网里不确定性参数种类多、相关性强、且缺乏足够的历史数据来精确刻画概率分布。随机优化需要给出概率分布模型,而鲁棒优化只需要一个区间范围,这在工程数据不足的情况下更容易落地。
3. 拿一个算例把模型落地:从确定性版本到鲁棒版本
3.1 算例配置和基础场景
为了把过程讲清楚,我建了一个不算复杂但足够说明问题的微电网算例。系统包含:140kW光伏、50kW风电、100kW/200kWh储能(最大充放电功率50kW)、60kW燃气轮机,以及与主网的联络线(最大购电100kW、最大售电50kW)。调度周期是24小时,时间间隔1小时,目标函数是最小化全天运行成本。
电网电价采用分时结构:峰时段1.0元/kWh、平时段0.6元/kWh、谷时段0.3元/kWh,售电价格统一按购电价格的50%计算。光伏预测采用“晴天型”曲线,中午11点到14点出力在120kW附近波动,全天预测误差边界设为各时段预测值的20%,而实际我在算例中还会模拟一个“午后光伏骤降”的场景来测试方案韧性。
3.2 确定性日前调度模型怎么写
先把基线版本,也就是确定性模型摆出来。目标函数是:
min Σt [ cbuy(t)·Pbuy(t) − csell(t)·Psell(t) ] + cgas·Σt Pgas(t)
其中cbuy、csell分别是购电和售电价格,Pbuy、Psell是购电和售电功率,cgas是燃气成本系数,Pgas是燃气机出力。因为售电价格低于购电价格,模型不会同时既买又卖,这个约束可以靠价格机制自然满足。
约束条件里,最核心的是每个时段的功率平衡:
Pbuy(t) − Psell(t) + Ppv(t) + Pwind(t) + Pgas(t) + Pdis(t) − Pch(t) = Pload(t)
储能部分用SOC状态变量串联各时段:
SOC(t) = SOC(t−1) + ηch·Pch(t)·Δt/Cap − Pdis(t)·Δt/(ηdis·Cap)
同时有SOC上下限、充放电功率上下限、联络线功率上限、燃气机爬坡约束。把这些约束和24个时段的决策变量一起丢给求解器,就是一个标准的线性规划(LP)问题,CPLEX或Gurobi几秒钟就能出解。
3.3 把光伏出力改成区间参数:数学上的具体改法
现在进入正题。把光伏出力从固定值Ppv^f(t)改成一个有边界的区间参数,允许它取到Ppv^f(t) − Δpv(t)到Ppv^f(t) + Δpv(t)之间的任意值。负荷也做同样处理,用一个Δload(t)表示预测误差边界。
直接的做法是把功率平衡约束改成“在最坏情况下也必须满足”的版本。用一个直观的0-1变量写法来说明:
Pbuy(t) − Psell(t) + Ppv^f(t) − zpv(t)·Δpv(t) + Pwind(t) + Pgas(t) + Pdis(t) − Pch(t) = Pload^f(t) + zload(t)·Δload(t)
其中zpv(t)和zload(t)是0-1标志变量,等于1表示该时段太阳能取最坏下限、负荷取最坏上限。再用一个预算约束串联起来:
Σt [ zpv(t) + zload(t) ] ≤ Γ
这样模型就变成了MIP(混合整数规划)。这种写法的优点是直观,一眼能看出“哪些时段最坏情况被激活了”;缺点是24个时段每个时段都要引入二进制变量,求解规模上升很快,如果系统再大、时段再密,计算时间会变得很难看。
所以实际工程里我更推荐用Bertsimas-Sim的对偶等价变换,把内层max问题通过线性规划对偶转成一组额外约束,完全不需要二进制变量,模型规模增长有限,求解速度比直接MIP快很多。这一步的推导细节这里不展开,直接用求解器代码落地更实际。
3.4 求解器落地与代码骨架
如果你用MATLAB环境,YALMIP自带鲁棒优化的建模扩展,可以直接声明不确定变量,然后照常写约束,求解器层面会自动处理对偶问题。我对这个方式保留一个态度:YALMIP的鲁棒扩展在中小规模算例上很好用,但到了大规模场景,它自动生成的等价模型往往不够紧凑,运行时间可能焦虑。
我更常用的是Python + Pyomo + Gurobi,手动把鲁棒约束写成对偶形式的显式约束。核心代码骨架大概是这样的:
from pyomo.environ import * from pyomo.opt import SolverFactory m = ConcreteModel() m.k = range(24) # 决策变量 m.Pbuy = Var(m.k, bounds=(0, 100), domain=Reals) m.Psell = Var(m.k, bounds=(0, 50), domain=Reals) m.Pch = Var(m.k, bounds=(0, 50), domain=Reals) m.Pdis = Var(m.k, bounds=(0, 50), domain=Reals) m.SOC = Var(m.k, bounds=(0.2, 0.9), domain=Reals) m.Pgas = Var(m.k, bounds=(0, 60), domain=Reals) # 鲁棒辅助变量:对偶变量 m.alpha = Var(m.k, domain=Reals) m.beta = Var(m.k, domain=Reals) m.gamma = Var(domain=Reals) # 目标函数(略) # 功率平衡的鲁棒化约束:将对偶后的形式直接写入 def balance_rule(m, t): return ( m.Pbuy[t] - m.Psell[t] + pv_f[t] + wind_f[t] + m.Pgas[t] + m.Pdis[t] - m.Pch[t] - m.alpha[t] * delta_pv[t] - m.beta[t] * delta_load[t] - m.gamma >= load_f[t] ) m.balance = Constraint(m.k, rule=balance_rule) # 对偶变量的范围约束 m.con_alpha = Constraint(m.k, rule=lambda m, t: -1 <= m.alpha[t] <= 1) m.con_beta = Constraint(m.k, rule=lambda m, t: -1 <= m.beta[t] <= 1) m.con_gamma = Constraint(expr=sum(m.alpha[t] + m.beta[t] for t in m.k) <= Gamma)这个骨架里,功率平衡约束不再是“预测值刚好相等”,而是“在预算所允许的最坏偏差下,系统依然能保住平衡”。注意对偶变量alpha、beta的可取值范围是[-1,1],这是Bertsimas-Sim标准形式里的特征,直接背下来用就行,但前提是你已知晓推导过程中的符号定义。
4. 结果不会骗人:确定性方案和鲁棒方案的真实差距
4.1 正常运行日的对比
先看预测完全准确的晴好日。确定性方案出的日运行成本大约是4510元,策略也很典型:白天光伏出力大,储能充到上限,晚上低谷电价时段电网充电,峰时段储能放电。鲁棒方案在这个场景下日成本大约4870元,比确定性方案贵了8%左右。这8%不是白白浪费的,你可以理解成给不确定性交的“保险费”。
这笔保费体现在什么地方?看调度曲线就清楚了:鲁棒方案在白天不会把储能电量用得太干净,SOC始终多留了10%到15%的余量;电网购电计划也比确定性方案更保守,避免了“把所有希望寄托在光伏出力峰值”这种脆弱策略。换句话说,确定性方案把系统压到了最优边界上,而鲁棒方案主动退后一步,给自己留了调整空间。
4.2 光伏出力的“灾难日”对比
为了验证鲁棒方案的真正价值,我构造了一个灾难场景:午后14点开始光伏出力从预测的120kW骤降到60kW,同时16点负荷比预测高了15%。这就是鲁棒模型里最坏情况的部分体现。
按确定性方案执行时,系统根本没有冗余:储能白天已经按计划放完,燃气机已经满发,联络线功率也顶到上限。面对光伏缺口,只能以现货高价补电,当天实际运行成本飙到6800元,比计划成本高出50%以上。注意这种成本暴涨在工程里还不是最可怕的,最可怕的是系统可能直接撞上联络线功率上限,甚至触发低频减载。
鲁棒方案在同样场景下的表现就好得多。因为建模时就把最坏情况纳入了约束,方案保留了足够的储能余量和燃气机爬坡空间,面对光伏骤降,只需要切一部分负荷、加一部分燃气出力、储能多放一点点,当天总成本约5470元,虽然比计划成本略高,但完全没有出现“超出物理极限”的窘境。两相对比,鲁棒方案多支付的保费在灾难日得到了几十倍的回报。
4.3 保守度调节:Gamma参数怎么权衡
Gamma的取值直接决定模型的保守程度。我在这个算例上做了敏感性测试:
| Gamma取值 | 正常运行日成本 | 灾难日实际成本 | 备注 |
|---|---|---|---|
| 0 | 4510元 | 6800元 | 退化为确定性优化,脆弱 |
| 4 | 4680元 | 5860元 | 保费低,抗风险一般 |
| 8 | 4870元 | 5470元 | 相对均衡 |
| 12 | 5100元 | 5310元 | 保费偏高,收益递减 |
| 24 | 5560元 | 5230元 | 过度保守,日常成本高企 |
可以看到,Gamma从0增加到8,日常成本涨了8%,但灾难日成本降了接近20%;Gamma再往上加,日常成本继续涨,灾难日成本下降幅度越来越小,说明边际效益递减。实际项目里我一般不会直接想“Gamma等于多少最好”,而是把历史场景回放一遍,画出“成本-可靠性”曲线,让业主根据对风险的容忍度来做选择。如果业主能接受偶尔1次、2次的高成本事故,Gamma可以取小一点;如果微电网是给精密制造或医院供电,Gamma就得往大调。
5. 实战里踩过的几个坑
5.1 不确定集边界不是拍脑袋定的
最早做鲁棒优化的时候,我吃了“边界拍脑袋”的亏。当时为了省事,直接把光伏预测误差边界定成±20%,建模跑完一看,方案保守得离谱,储能整天不敢充,燃气机一直在备用。后来我把过去三个月的光伏预测和实测数据拿出来统计,发现不同时段的预测误差差异很大:早晨和傍晚云层复杂,误差在30%以上;中午晴朗时段稳定,误差只有10%左右。按时段动态设定Δpv之后,模型保守度立刻下来了,成本也更合理。
所以我的建议是,不确定集边界必须来自历史误差分布,一般取90%或95%分位数。如果项目里没有足够历史数据,至少也要参考气象预报的置信度来动态调整。千万别拍脑袋定百分比,否则你的“鲁棒”只是给自己的模型添堵。
5.2 鲁棒解不是万无一失,实时修正照样重要
还有一个容易产生的误解:用了鲁棒优化,日前方案就一定能扛住所有情况。实际不是。鲁棒优化只保证不确定集范围内的最坏情况可以应对,一旦实际偏差超过了预设边界,方案照样失效。比如暴雨天光伏出力比预测低50%,而你在不确定集里只考虑到了20%,那什么优化都救不了你。
所以我在实际项目里用的是一个“日前鲁棒 + 日内滚动修正”的双层结构。日前层用鲁棒优化定大方向:燃气机启停、购电大水、储能的安全区间;日内层用滚动MPC,每15分钟基于最新实测数据做局部调整。鲁棒优化负责缓解“没有修正能力时”的系统性风险,MPC负责处理短时扰动。两者不是替代关系,而是配合关系。
5.3 对偶变换里的符号容易翻车
Bertsimas-Sim的对偶形式看起来很简单,但实际写代码时非常容易在符号上翻车。我踩过最典型的坑是:把原问题中“≥”方向的不确定约束直接套用对偶公式,结果等价的辅助变量取反了,解出来之后一检查,功率平衡在大多数场景下都不满足,而且还很难发现,因为MIP不报错,目标函数也正常。
现在我的习惯是,每次写完鲁棒约束,都会做一次“场景验证”:随机生成上百组不确定参数的真实场景(不超出不确定集),把求出的决策代回去检查约束是否全部满足。这种随机验证不用很久,但能非常快地暴露对偶符号写反、预算约束写漏这类低级错误。
5.4 别把随机优化和鲁棒优化混着用
最后提醒一下场景选择问题。鲁棒优化的优势是数据需求低、保证性强,但有代价:它对“概率意义上更可能发生”的普通偏差并不敏感,模型只盯着最坏情况,这就导致它在平常日子里牺牲了一定的经济性。
如果项目有丰富的历史数据,能建出比较可靠的概率分布模型,那随机优化(比如样本平均近似、场景树方法)可能在同样成本约束下给出更经济的方案。这不是谁取代谁的问题,而是数据量和风险偏好的选择问题。我自己的经验是:数据少、风险厌恶、硬性可靠性约束多的时候选鲁棒;数据多、允许一定概率失配、追求平均经济性的时候选随机优化。两者结合的两阶段鲁棒-随机混合模型,是更进阶的方向,但对团队建模能力和求解资源的要求也更高。
总的来说,微电网调度从来不是“算出一个最优解”那么简单。系统越复杂、分布式电源占比越高,不确定性就越突出,调度方案离“鲁棒”二字就越近。鲁棒优化不是一个花哨的数学概念,它本质上就是让你在不确定性面前保持从容的工具。如果你正在做微电网EMS,建议先拿历史数据把误差分布摸清楚,再上手建模,这条路走通之后,你会明显感觉到调度方案从“看着最优”变成了“跑起来也不慌”。