1. 先说清楚:这个优化问题到底在优化什么
我第一次看到"计及需求侧响应日前、日内两阶段鲁棒备用优化"这个标题的时候,第一反应是:这又是一个把三个热门概念缝在一起的学术题目。但真正把模型推完、代码跑通之后,我发现它其实是一条非常清晰的技术主线——用需求侧响应资源当备用,用鲁棒优化来对付不确定性,用日前日内两阶段来做决策分工。
先说需求侧响应。传统电力系统里,备用容量基本是靠火电、水电这些发电侧资源来扛的,系统频率跌了、机组跳了,旋转备用马上顶上。但近几年分布式光伏、风电渗透率越来越高,负荷侧的柔性负荷、储能、电动汽车充电桩也都具备短时调功率的能力。需求侧响应(Demand Response,DR)就是把这部分用户侧可调节资源引入调度,让它们在系统需要的时候削减用电或调整用电时段,相当于给调度员增加了备用来源。它的响应速度可以做到秒级到分钟级,比冷备用机组热启动快得多。
再说为什么非要"日前、日内"两阶段。日前阶段是提前一天做决策,那时候风电出力的预测误差还比较大,只能定一些"慢变量",比如机组启停状态、DR资源的签约容量、备用容量的购买计划。日内阶段是接近实时运行的时候,预测精度已经大幅提升,这时候才针对具体的不确定性场景做"快变量"调整,比如实时出清、备用调用、切负荷决策。这种两阶段结构本质上借鉴了随机规划里的"这里-现在"决策拆分,只是把随机场景换成了鲁棒集合。
然后就是鲁棒备用优化。很多文献里用的是随机规划,要假设误差的概率分布,然后用场景法或者机会约束来建模。但随机规划的痛点在于分布假设经常不靠谱,尾部分布稍微偏差一点,结果就会过度乐观。鲁棒优化走的是另一条路:给定一个不确定性集合,保证最坏情况下的系统安全。代价是结果偏保守,但换来的是强鲁棒性。在备用优化里,鲁棒性意味着——不管风光出力在集合内怎么波动,系统都能靠备用容量扛住,不切负荷、不违反约束。
所以这个题目落地的目标就是:用Matlab实现一套完整的优化模型和求解算法,把需求侧响应资源作为备用来源,在日前阶段决策备用容量和其他慢变量,在日内阶段根据不确定性集合动态调整调用计划,保证任何可能的风光/负荷偏差下系统都能安全稳定运行。这篇博文适合三类读者:一是正在做电力系统优化方向的研究生,需要一套可复现的Matlab代码框架作为参考;二是做能源调度系统开发的工程师,想了解鲁棒优化的工程落地方式;三是刚接触鲁棒优化的初学者,想通过一个具体案例把min-max-min结构、C&CG算法这些抽象概念落到实处。
我在复现和改写这套代码的时候遇到过很多坑:两阶段迭代不收敛、场景生成过于保守导致成本虚高、CPLEX与Matlab接口的矩阵维度不一致、日内决策变量和日前决策变量搞混导致语义错误……这些坑下面都会逐一拆解。先不急着贴代码,把模型和算法思路理清楚了,代码其实只是水到渠成的事情。
2. 从目标函数到约束条件:先把数学模型搭起来
做优化仿真的第一件事,不是急着敲代码,而是把优化问题写成标准形式。否则你写出来的脚本只是一堆赋值代码,改一个参数就要重写一大段,毫无可扩展性。这里的数学模型分四层:决策变量的时间分层、目标函数构成、约束体系、不确定集合的定义方式。
2.1 决策变量的时间分层:哪些变量放日前,哪些放日内
这是整个模型设计的核心分水岭。按照两阶段鲁棒优化的通用框架,第一阶段的决策变量往往是与"是否运行"和"备用容量配置"相关的变量,第二阶段则是与"实际功率调整"相关的变量。
放在日前(第一阶段)的变量包括:
- 机组启停状态变量:这是典型的0-1整数变量,一旦定了,日内阶段不能改。
- DR资源的签约容量:比如某用户聚合商承诺可削减10MW,这部分在日前签约,日内按需调用。
- 备用容量购买量:在日前市场购买的上、下备用容量,可以是系统备用,也可以是某台机组预留的备用容量。
放在日内(第二阶段)的变量包括:
- 机组实际出力增量、DR实际削减量,这些要等不确定参数实现之后再确定。
- 切负荷量、弃风量、弃光量,作为惩罚项存在的松弛变量。
- 功率平衡约束中需要实时调整的其他变量。
这里要特别强调一点:不要把不确定参数直接当决策变量建模。很多人初学鲁棒优化,会把风电出力设成变量然后取最坏值,这样跑出来结果很怪。正确做法是:风电出力、负荷预测误差这些是"外部参数",它们取值来自不确定性集合,决策变量是在这些取值下仍然可行的调整量。鲁棒优化的本质不是优化不确定参数,而是寻找对不确定性集合内所有取值都可行的决策。
2.2 目标函数:成本组成没那么复杂,但权重很讲究
目标函数通常是最小化总成本,包含以下几个部分:
第一部分是日前阶段的基准运行成本,包括机组发电成本、启停成本、购买备用容量的成本、DR签约补偿成本。发电成本我习惯用一个二次函数拟合:c2P^2 + c1P + c0,用分段线性化处理之后再交给求解器。DR签约成本则是单位容量签约价格乘以签约量。
第二部分是日内阶段针对最坏场景的调整成本,包括DR调用补偿、机组增发/减发的成本变化、以及惩罚项成本。惩罚项非常重要——切负荷的惩罚系数往往设为几千到上万元每MWh,弃风弃光的惩罚系数次之。这样设计是为了让优化结果优先用备用容量扛住不确定性,实在不行才切负荷。
两阶段目标函数写成数学形式就是:
min F(x) + max_{u in U} min_{y in F(x,u)} G(y)
这里x是日前变量,u是不确定参数,y是日内变量,F(x)是第一阶段成本,G(y)是第二阶段成本。内层的min就是给定场景下的经济调度问题,外层的max就是寻找最坏场景,整体就是一个典型的min-max-min结构。很多初学者一看到这种嵌套结构就懵了,其实拆开看就是两个优化问题套在一起,下面第三节会讲怎么用C&CG算法把它拆解成可以迭代求解的形式。
2.3 约束体系:功率平衡、备用约束、DR调用约束缺一不可
约束条件我按功能分成四类:
功率平衡约束:这是最基本的等式约束,各机组出力加风电光伏出力加DR削减量,等于负荷需求。在两阶段结构里,日前约束用的是预测值,日内约束用的是任一不确定取值下的实时值。风电光伏出力和负荷都是不确定参数,所以这条约束在第二阶段要保证对集合内所有场景都成立。
备用容量约束:系统上调备用至少要覆盖最坏场景下的净负荷波动,下调备用同理。这里要注意,备用的来源不止是机组,DR资源和储能也可以算作备用容量的一部分。在备用约束里要给DR资源的响应特性加一个限制:比如DR最大响应时间超过15分钟,那它就不能算作一次调频备用,只能算作旋转备用或非旋转备用。
DR资源调用约束:DR签约容量是上限,日内实际调用量不能超过这个上限。另外DR资源往往有持续响应时间限制,比如某类工业负荷只能削减1小时,那它在日内调度时段上的连续削减时间就要受到限制。这类"时间耦合约束"在日前建模时就要引入,否则日内阶段可能出现不符合物理特性的调用方案。
机组运行约束:出力上下限、爬坡约束、最小启停时间约束。在日内阶段,机组出力是在日前计划基础上的调整量,爬坡约束要体现出力的调整速度限制。两阶段模型里机组爬坡约束需要同时考虑日前与日内两个阶段的变量,也就是要保证"日前计划出力+日内调整量"不超过机组的物理爬坡能力。
2.4 不确定性集合:盒式、预算约束和椭球式怎么选
鲁棒优化的关键就在不确定性集合的构造。工程上用得最多的是盒式集合,也就是每个不确定参数都在一个区间内独立波动:
U = {u | u_min <= u <= u_max}
这种集合的优点是建模简单,求解快,但缺点也很直接——它允许所有不确定参数同时达到最坏值,导致结果过于保守。实际中风电场的出力不可能所有机组同时满偏,所以就有了预算约束的改进版:
U = {u | u_min <= u <= u_max, sum(|u - u_nom| / (u_max - u_nom)) <= Gamma}
这里的Gamma就是不确定性预算,它限制了总共能偏离预测值多少个"单位幅度"。Gamma越大越保守,Gamma=0就退化成确定性模型。在Matlab代码里,我建议把Gamma设成一个可调的输入参数,这样灵敏度分析的时候非常方便——固定其他参数只改Gamma,就能直接画出成本随保守程度变化的曲线,这是论文和项目汇报里很好用的一张图。
还有一类是椭球式集合,用二次约束来限制不确定参数的联合波动范围。椭球集合的好处是能捕捉参数间的相关性,但求解时会把模型变成二阶锥规划(SOCP)问题,对求解器的要求更高。我个人在备用优化这个场景里更推荐预算约束集合,因为:电力系统备用优化中需要保证的是绝对安全边界,预算约束能直观控制保守程度;而且盒式加预算约束保持了模型的线性结构,可以直接用C&CG加大M法、Benders分解或者商业求解器来解,不需要额外引入锥规划求解器。
3. Matlab实现:一个可复现的代码框架是怎么搭出来的
模型纸上谈兵是没有价值的,真正跑出数字才是硬道理。这一节我直接把代码框架拆给你看,不贴完整工程(太长),但把每个模块的职责、核心数据结构和关键代码逻辑讲清楚。
3.1 顶层运行流程:数据进来,结果出去,中间就三步
我的代码框架按数据流可以分成三个大阶段:
数据准备阶段:生成或读取系统参数(发电机参数、负荷曲线、风电预测区间、DR资源数据),初始化优化器配置。我习惯把系统数据都封装成结构体(struct),比如gen_data、load_data、wind_data、dr_data,这样主程序里传参非常清晰。
模型构建阶段:构建日前主问题(MP)和日内子问题(SP)的数学表达式,包括变量定义、目标函数、约束条件。在Matlab里,我强烈建议用YALMIP工具箱来建模,而不是直接用求解器的原生API。YALMIP支持符号化建模,写约束条件跟写数学公式差不多,非常直观,而且底层支持CPLEX、Gurobi、Mosek等多个求解器的切换。
迭代求解阶段:调用C&CG算法框架,在主问题和子问题之间循环迭代,直到满足收敛条件。收敛之后输出日前决策值、日内最坏场景成本、备用配置结果、DR调用计划等一系列结果。
核心运行文件大概就是run_optimization.m这样一个主脚本,里面设置好参数后依次调用data_input.m、build_MP.m、build_SP.m、solve_CCG.m、result_output.m这五个函数。这种模块化设计的好处是:你想换一个需求响应模型,只需要改dr_data和build_MP里的相应约束;你想改不确定集合,只需要改build_SP里的U定义。
3.2 YALMIP建模的核心思路:符号变量定义好后,约束就是自然语言
YALMIP里定义一个变量特别简单,比如:
x = sdpvar(n_units, T); % 机组出力 z = binvar(n_units, T); % 机组启停状态 r_up = sdpvar(n_units, T); % 上调备用 dr_sig = sdpvar(n_dr, T); % DR签约容量 dr_call = sdpvar(n_dr, T); % DR日内调用量目标函数和约束条件就像写数学公式一样堆上去:
Constraints = []; Constraints = [Constraints, P_g_min .* z <= x <= P_g_max .* z]; Constraints = [Constraints, sum(x) + sum(wind_pred) == load_pred - sum(dr_call)]; Constraints = [Constraints, 0 <= dr_call <= dr_sig];然后指定求解器调用:
optimize(Constraints, Objective, sdpsettings('solver', 'cplex'));这就是YALMIP最舒服的地方——你不用关心矩阵应该拼成什么维度,它自己会帮你转化。对很多刚起步的研究生来说,这是最大的效率提升。但注意,YALMIP并不是万能的,当你的模型规模特别大、约束特别多的时候,它的建模开销会比直接API高不少,这时候再考虑用Gurobi的Matlab接口直接建模也不晚。
3.3 主问题和子问题的代码结构:C&CG两阶段求解的关键写法
C&CG(Column and Constraint Generation)算法是求解两阶段鲁棒优化最主流的算法,它的核心思想是:把原问题拆成一个主问题(MP)和一个子问题(SP),通过迭代逐步逼近最优解。主问题是一个包含"最坏场景"的松弛问题,子问题是在给定第一阶段决策下寻找最坏不确定场景。关键技巧是:每轮迭代会把子问题找到的最坏场景对应的第二阶段变量和约束"列生成"到主问题里,所以主问题会越变越大,直到收敛。
主问题(MP)的YALMIP伪代码大概是这样:
x = sdpvar(...); omega = sdpvar(...); for k = 1:K % K是已迭代次数 y{k} = sdpvar(...); % 引入历史场景对应的第二阶段变量 Constraints = [Constraints, ... 添加该场景下的约束 ...]; end Objective = f'*x + max_cost; % max_cost是辅助变量 optimize(Constraints, Objective, options);子问题(SP)的求解要更小心,因为SP本身是一个inner max-min问题。处理它的标准做法是:将内层的min问题写成KKT条件(强对偶成立时),替换成单层max问题。如果内层是线性规划,直接对偶变换即可。在Matlab里我常用dual函数配合YALMIP求解对偶问题,但更推荐的方法是直接用KKT条件或者用recover命令处理。很多做鲁棒优化的朋友卡在了子问题的对偶转换上,我的经验是推导先手写一次,搞清楚对偶变量和原变量的对应关系,再写代码,否则代码报错的时候你会被矩阵维度搞得完全懵圈。
3.4 场景生成:预测区间和误差分布怎么设定最合理
不确定性集合的数值不是拍脑袋写的,需要结合预测结果。我对风电出力的处理方式是:读取历史预测数据和实测数据的误差,计算每个时段预测误差的标准差sigma(t),然后设不确定区间为[wind_pred - ksigma(t), wind_pred + ksigma(t)],k一般取1.5到2.5。
负荷预测误差的处理思路类似,但注意负荷的变化比风电平滑,误差比例更小,可以设定为预测值的2%到5%。这些误差参数要作为config结构的字段集中管理,方便统一调整:
config.wind_sigma_mult = 2.0; % 风电误差倍数 config.load_error_pct = 0.03; % 负荷误差百分比 config.gamma = 12; % 不确定性预算很多刚接触这个方向的朋友容易忽略的是:DR资源本身也存在响应不确定性。比如你说好削减10MW,实际可能只削减了8MW。这种不确定性在建模里可以处理为日内实际可用DR容量是一个不确定参数,从而引入到子问题的不确定集合中。但这样会让模型复杂度大幅上升,建议初版先不加,跑通之后再扩展。
3.5 环境准备和工具箱选型:从Matlab安装到求解器配置
看热搜词里大量关于Matlab安装、工具箱、许可证、启动日志的问题,我顺手把仿真环境搭一下。先说你需要的软件栈:
Matlab本体:建议2020b以上版本,因为YALMIP对旧版的支持越来越弱;2024a/2025b这种新版本对我的脚本也没有兼容问题。
YALMIP工具箱:直接在GitHub上下载zip包,解压后把路径加到Matlab里(set path),或者在主页下载release版。装好后在命令行输入yalmiptest,看到all tests passed就算成功了。
求解器:CPLEX和Gurobi选一个就行。Gurobi的学术license申请很简单,用学校邮箱就能拿到。安装之后最关键的一步是运行gurobi_setup,把Gurobi的Matlab接口加到路径里,否则YALMIP认不出它。
常见的问题都集中在路径配置上:要么是YALMIP路径没加全,要么是Gurobi接口文件没编译好。我记得有个朋友安装了Gurobi之后一直报license error 9,后来发现是他的license文件路径没写对,环境变量GBR_LICENSE_FILE没有指向正确位置,改完之后就正常了。还有一个很常见的问题是Matlab远程桌面环境下license打不开,特别是浮点license,建议把license server的端口在防火墙里放行,或者直接用单机版license跑仿真。
注意:不建议安装网上的破解版或密钥工具,一方面有安全风险,另一方面Gurobi/CPLEX这类商业求解器升级换代很快,用正版学术License最稳妥,成本也不高。
4. 求解过程中的调试经验:做鲁棒优化最容易卡住的地方
写代码容易,调代码才是真正耗时间的地方。两阶段鲁棒优化比普通优化模型多了迭代求解的环节,所以调试难度高了一大截。这些年我总结出了四个最容易翻车的地方。
4.1 迭代不收敛:C&CG算法最常见的三个根因
C&CG迭代不收敛的表现是上下界之间的gap不下降或者振荡。造成这种情况的原因通常有三个。
第一个是子问题求解不对,特别是对偶变换里的符号问题。子问题是max-min结构,min部分是线性规划时通过强对偶转成max。但强对偶成立的前提是原问题有可行解且有界,如果你的子问题在某个迭代轮次中不可行,对偶就会出问题。处理方式是给子问题里加松弛变量,让模型始终有可行解,但是对松弛变量施加高惩罚,这样即使不收敛也不至于报错。
第二个是主问题忘了把"历史场景"的约束加进去。C&CG算法里,每一轮子问题求出的最坏场景,都要生成对应的第二阶段变量和约束,追加到主问题里。如果只更新目标函数下界而不更新约束集,主问题就会被上一个场景"绑架",迭代肯定乱套。
第三个是收敛容差设得太严。有些文献为了结果精度把gap容差设为1e-6,这在中小规模算例里勉强能跑,但规模上去之后要迭代上百轮,且多数场景下的最优gap已经很小了。我建议默认设1e-3到1e-4,工程上完全够用。
4.2 不确定性集合太保守:成本虚高怎么定位
鲁棒优化的结果本身就偏保守,但如果保守到离谱,就要检查是不是集合构造出了问题。
首先检查预测区间设置。如果你设的k倍标准差太大,等于把所有出力的最极端波动都囊括了进来,自然会得到非常保守的结果。解决办法是一开始用较小的k值(比如1.5),然后做灵敏度分析,看成本随k的变化曲线,选一个合理拐点。
其次检查Gamma预算的设定。Gamma物理含义是"不确定参数偏离预测值的总幅度上限",如果你的系统有100个不确定参数,Gamma设为100就退化成了盒式集合,极端保守。实际工程上Gamma的取值范围一般是总不确定参数数量的10%到30%,也就是说让少量参数达到极端值,大部分参数在正常范围内波动,这样既保证鲁棒性又不过分激进。
还可以对比一下鲁棒解和随机解的成本差距。如果差距在5%以内,说明你的鲁棒模型设计得比较合理;如果差20%以上,先回头检查是否把不确定性集合定义得过宽,再看成本构成中是不是惩罚项设置不合理,或者备用容量购买成本过高。
4.3 变量维度错误:YALMIP报错信息不直观怎么办
YALMIP报错的典型信息是Size mismatch in the constraint之类的。这时候不要盯着报错行看,而是要逐条检查每个约束的左右边变量维度。我自己常用的调试方法有三个:
在建模之前先用size命令打印所有主要变量的维度,确保它们的维度符合预期。这个虽然笨,但有效率极高。
分段注释法:把约束分成几组,每组都运行一次,看加哪一组约束的时候开始报错,就能迅速定位问题。
利用YALMIP的check命令。求解完之后运行check(Constraints),它会输出每条约束的残差。如果某条约束的残差特别大,对应的就是问题所在。
另外要注意,如果子问题是解析计算出来的场景,要在迭代过程中打印每一轮的上界值和下界值,这样能直观看到gap变化趋势。如果下界值一直不变,说明主问题可能卡在同一个场景里;如果上界一直不下降,说明子问题找的最坏场景没有让主问题更新。这种诊断信息比任何调试器都有用。
4.4 求解时间爆炸:模型规模大时的加速思路
两阶段鲁棒优化的计算瓶颈主要在子问题的反复求解上。特别是当系统节点数几十个、机组十几个、时段96个时,每轮子问题都要解一次混合整数规划(如果子问题里有整数变量的话),迭代几十轮下来时间会非常可观。
加速手段我验证过几种:
一是能线性化就绝不引入整数变量。子问题里尽量保持线性规划结构,因为LP的求解速度比MIP快几个数量级。很多人在日内调度里为机组启停引入整数变量,导致子问题变成MIP,直接拖垮时间。实际上日内阶段机组的启停状态已经在日前确定了,日内只调出力大小,所以子问题应该设计成纯线性规划。
二是利用强对偶或KKT条件把双层问题转换为单层问题后,可以用单次求解替代内层迭代。这样虽然单次求解的计算量增大了,但避免了双层嵌套的严重性能衰减。
三是使用并行计算。如果用了场景法作为对照验证,每个场景的优化问题相互独立,可以用parfor并行求解,提速很可观。
四是设置合理的时间限制和MIP gap。在sdpsettings里设置'cplex.mip.tolerances.mipgap', 0.01,求解器会在接近最优时提前退出,大大减少迭代时间。但在论文实验里别用太松的gap值,不然评审会刁难你。
4.5 从单时段扩展到多时段:时间耦合约束的引入
很多人的初版代码是先做单时段模型,验证算法逻辑之后再扩展到多时段。这种思路没问题,但扩展的时候要注意时间耦合约束的处理。
DR资源的响应约束往往是跨时段耦合的。比如某类可中断负荷一旦被调用,至少要持续中断2小时,这就要在相邻时段之间建立约束。机组的最小启停时间和爬坡约束也是典型的跨时段约束。在多时段模型里,主问题需要在日前阶段就加入这些跨时段约束,否则日内调度出来的方案物理上根本不可行。
还有一种更复杂的扩展是DR资源的"恢复特性"。很多柔性负荷比如空调群,在削减一段时间之后会有回弹效应,用电量会在之后某个时段报复性增长。这种跨时段的能量守恒约束建模起来比较麻烦,但如果做需求侧响应调度而不考虑回弹效应,实际执行的时候系统可能被二次冲击。我在代码里一般是给DR资源加一个累积能量约束:调用期间削减的总电量不能超过某个上限,同时预留恢复时段的功率增加空间。这样既贴近工程实际,又不会让模型复杂到解不出来。
5. 算例设计和代码验证:证明模型有效性的标准动作
模型和算法都通了之后,还需要设计一套完整的算例实验来验证。这一步既是论文和项目交付的需要,也是你确认代码没有隐性bug的必要手段。
5.1 基线对照:没有对比就看不出模型价值
第一步是跑一个确定性模型作为基线。把不确定性集合设为只有预测值那一个点,相当于Gamma=0,此时两阶段鲁棒优化退化成单阶段确定性优化。这个基线结果反映了理想情况下的最低成本,也是所有对比的基础。
第二步是跑盒式集合下的鲁棒模型,也就是Gamma取最大值,此时所有不确定参数都能同时达到边界值。这个结果反映了完全保守情况下的最高成本。
第三步是跑不同Gamma值下的预算鲁棒模型,观察成本从最低到最高的变化曲线。理想情况下,这条曲线应该是平滑上升的,意味着你可以在鲁棒性和经济性之间选择不同的平衡点。
5.2 DR资源参与与否的对比实验
这个实验最直观:第一组跑不含DR资源的传统备用模型,第二组跑含DR资源的模型。两组对比可以看出DR资源的参与能降低多少备用容量购买成本和总运行成本。
我在实际算例中经常得到这样的结果:含DR资源的模型,备用购买成本下降了20%到30%,总运行成本下降了5%到8%。原因很好理解,DR资源的备用报价通常比机组备用低,而且DR不消耗燃料,只要给用户补偿就行。更重要的是,在日内最坏场景下DR资源响应快,不需要考虑爬坡率限制,所以在紧急时刻能提供的有效备用更多。
5.3 参数灵敏度分析:哪些参数对你的结果影响最大
我的经验是画出三张灵敏度图:
- 成本-不确定性预算Gamma曲线:横轴Gamma,纵轴总成本,看成本随保守程度的变化趋势。
- 备用配置-DR价格曲线:横轴DR资源的价格参数,纵轴最优备用配置中DR参与的占比,看DR资源的市场竞争力。
- 切负荷量-备用容量曲线:横轴备用容量系数,纵轴最坏场景下的切负荷量,看备用冗余度与可靠性的权衡。
这三张图不仅验证模型行为,还能为后续工程落地提供决策依据。比如如果DR价格降到某阈值以下时DR参与量急剧上升,说明DR资源在经济性上有明显优势,调度中心就应该通过市场机制吸引更多DR资源参与备用市场,而不是只靠行政指令。
5.4 结果可视化:Matlab画图的小技巧
鲁棒优化结果的好几张图是必须的:日前决策图(机组启停计划、备用容量配置)、日内场景图(最坏场景下的功率平衡情况)、以及DR调用时序图。画图代码我习惯用Matlab自带的绘图函数,注意几个细节:
- 用
stairs画离散时段的机组出力,比plot更像调度结果。 - 在图上用
patch或area填充DR调用区间,一眼就能看清哪些时段启用了DR。 - 如果画系统净负荷曲线,推荐叠加显示预测区间和实际最坏场景曲线,让不确定性的影响一目了然。
颜色上建议统一风格,比如发电出力用黑色/灰色系,DR用红色系,风电光伏用蓝色系,这样项目汇报和论文配图都拿得出手。
6. 这套代码可以扩展到的几个方向
代码如果只是复现论文里的一个算例,生命力是有限的。我实际在这套框架上做的扩展至少有三个方向,未来你可能也用得上。
6.1 加入储能系统作为备用资源
储能和DR虽然都在用户侧或电网侧,但储能的可控性更强,调节范围双向(可以充电也可以放电),并且持续时间通常比DR的响应窗口长。在模型里加入储能后,第二阶段决策变量会增加储能的充放电功率,约束需要加入SOC(荷电状态)的时间递推关系:
SOC(t+1) = SOC(t) + eta_ch*P_ch(t) - P_dis(t)/eta_dis
这个递推关系是跨时段的,会让主问题的规模明显增大,但C&CG框架完全兼容。扩展之后可以做DR和储能协同优化的对比实验,看看两类灵活性资源在备用表现上的互补性。
6.2 从单节点孤岛系统扩展到输配协同系统
如果研究的是配电网层面的备用优化,节点电压约束和支路潮流约束就不得不考虑。把单节点功率平衡约束替换为Distflow潮流方程,原来的线性模型就会变成二阶锥规划(SOCP)问题,求解器需要支持Mosek或者Gurobi的CONIC模式。
这种扩展的关键挑战是:两阶段鲁棒优化加潮流约束后,子问题的对偶推导会变得非常复杂。这时候我建议放弃解析C&CG,改用场景驱动的近似方法,或者把潮流约束线性化(比如忽略网损、假设平坦电压剖面),在系统规模较小的时候精度完全可以接受。
6.3 用强化学习替代一部分日内决策
日内阶段的决策是一个实时响应问题,如果每天要做96个时段的滚动优化,计算时间可能不足以支持在线决策。一个思路是:用今天的离线C&CG求解大量的日前决策和日内对应关系数据,训练一个代理模型在线映射"预测值->日内调整量",在部署时跳过优化直接查表或走神经网络推理。
这个方向我个人还在探索中,但基于这套代码框架做离线数据生成和验证闭环非常方便,因为你已经有完整的主问题、子问题和迭代求解器,生成的训练数据量级完全可控。说了这么多,核心是想告诉你:无论你怎么改代码,数据结构的清晰性和求解框架的模块化都是最重要的。我在跑这一系列鲁棒备用优化仿真的时候,踩了无数个变量维度对不上、约束方向写反、子问题无界的坑,最后发现真正救我的不是某个神奇的求解技巧,而是一开始就坚持用结构体管理数据、坚持把模型求解和数据分析解耦、坚持每次调试都打印中间结果对比验证。希望这篇博文能帮你把路走得更顺一点,少踩几个我当年踩过的坑。