ARTICLE DETAIL

资讯详情

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

微电网最优运行中的鲁棒优化与随机规划:非预见性约束的建模与求解

微电网最优运行中的鲁棒优化与随机规划:非预见性约束的建模与求解 1. 项目概述与问题建模1.1 微电网最优运行的核心需求先说清楚这个项目到底在做什么。我们说的“含可再生能源与储能的区域微电网”可以理解为一个小型的电力系统里面既有光伏、风机这类不可控电源也有储能电池这种可调资源同时还连接着本地负荷甚至可能和上级电网有交换功率的窗口。它的“最优运行”是指在满足所有设备物理约束和负荷需求的前提下通过安排机组出力、储能充放电、与外网购售电计划让某一段时间内的综合成本最低同时兼顾环保和可靠性。但问题难点在于光伏和风电出力受天气影响负荷也有波动如果只按照一个确定性的预测值来做优化结果往往在实际运行中无法执行。比如预测明天中午光伏大发调度计划里让储能少充电、多放电结果第二天云层一厚光伏骤降储能又因为之前放电计划而处于低电量状态那就只能高价购电甚至切负荷。所以这个项目的关键词是“不确定性”以及围绕不确定性提出的“鲁棒性”和“非预见性”。复现SCI论文的过程本质上是把这套理论落到Matlab代码里跑出可信的结果而不是盲目抄公式。这个项目适合谁如果你是做微电网能量管理、储能调度、电力系统优化的研究生或者工程师又恰好对鲁棒优化、随机规划不太熟悉想通过一套完整代码理解其中的建模与求解技巧那么这篇文章能帮你省下大量翻论文和debug的时间。我下面会从数学模型到代码实现再到我实际踩过的坑完整走一遍。1.2 不确定性来源与建模思路微电网里的不确定性主要来自两个方向一是可再生能源出力的间歇性二是负荷预测误差。前者通常用历史数据拟合概率分布或者直接采用预测区间后者在短时调度里同样重要但很多入门教程会简化成固定误差带。建模不确定性的主流通用思路有几类鲁棒优化、随机规划、机会约束规划。它们之间的关系可以这么理解随机规划假设不确定参数服从已知分布通过多个离散场景来逼近期望成本鲁棒优化则是在一个集合不确定集内寻找最坏情况下也能满足约束的解机会约束允许约束在很小的概率下被违反把概率约束转化为确定性形式。标题里同时提到了“鲁棒性”和“非预见性”这意味着论文大概率采用了随机规划框架并引入鲁棒约束来增强解的可靠性。非预见性约束是随机规划里“此刻决策不能看到未来场景实现”的数学表达后面我会专门讲。回到这个项目的实质我们的目标是在日前调度阶段就能给出一个可执行且对抗不确定性的计划。如果只用随机规划生成的解可能在部分极端场景下失稳如果只用鲁棒优化结果往往过于保守经济性差。因此很多SCI论文会采用“分布鲁棒优化”或者“两阶段随机鲁棒修正”的方式来综合而这个项目标题里强调“解鲁棒性与非预见性”说明它重视解的可行性和信息结构的正确性而非单纯追求最坏情况下的最优。1.3 鲁棒优化与随机规划的选择很多初学者一上来就问“到底该用鲁棒还是随机”我的建议是先看决策结构。如果微电网运行需要做日前决策比如储能的启停、与大电网的日前购电协议且这些决策必须在看到实际风光出力之前定下来那就非常适合用随机规划。因为随机规划天然通过场景树来表达“现在决策→未来实现→再决策”的时序关系而非预见性约束正好保证第一阶段的决策不依赖于未来的具体场景。而鲁棒优化更适用于“我只在乎最坏情况不过线”的场景比如系统必须保证在极端出力下不切负荷。它的计算通常比大规模随机规划更快但保守性也明显。实际项目里我见过很多论文把两者结合第一阶段用鲁棒约束保证可行性第二阶段用随机场景优化期望成本这样既不会过于保守又避免了场景数爆炸。这个项目标题里的“解鲁棒性”可能指的就是这个方向——即求出的解对于不同场景或者不同不确定集都具有一定的鲁棒可行性。我在复现时选择了带非预见性约束的随机规划模型作为主轴再对其中关键约束功率平衡、储能SOC始终限值添加鲁棒修正项。这个选择的好处是代码结构清晰能分别验证随机场景带来的期望收益和鲁棒修正带来的防御代价论文结果也容易解读。2. 数学模型构建与关键约束拆解2.1 目标函数设计经济性与低碳性最优运行的第一步是定义目标函数。常见微电网优化目标是最小化总运行成本包括购电成本、燃料成本、储能退化成本以及可能的碳排放惩罚。如果论文强调低碳则会加入碳交易成本或者排放系数加权项。我在代码里采用的目标函数表达式如下以一天24h调度间隔1h为例min Σ_t ( π_t^buy * P_t^buy - π_t^sell * P_t^sell c_fuel * P_t^DG c_deg * (P_t^ch P_t^dis) c_carbon * E_t )其中π_t^buy和π_t^sell分别表示分时购电、售电价P_t^buy为购电功率P_t^sell为售电功率P_t^DG是柴油机或燃气轮机出力P_t^ch、P_t^dis为储能充放电功率E_t为碳排放量各项系数是成本单价。目标函数不必一次写得太复杂先跑通再逐步加项否则模型规模大、调试困难。实际操作中要注意储能退化成本有两种建模方式一种按充放电量线性计费另一种按SOC循环深度计算。后者更精确但会引入非线性求解慢。我的复现里采用线性退化成本并且在SOC约束里做了放行边界这样既能在论文中体现储能寿命损耗又不至于把模型变成难解的MINLP。2.2 功率平衡与可再生能源出力模型功率平衡约束是每个时刻都必须满足的P_t^PV P_t^WT P_t^DG P_t^dis P_t^buy P_t^load P_t^ch P_t^sell其中P_t^PV和P_t^WT是光伏和风电出力。在随机规划中它们不再是固定值而是依赖于场景ω即P_{t,ω}^PV、P_{t,ω}^WT。相应地负荷也可以是场景相关的P_{t,ω}^load。很多复现代码会把可再生能源出力处理成“最大实际可用功率”然后允许丢弃部分出力即弃光、弃风。此时要引入弃光变量P_{t,ω}^PV_curtail约束变为P_{t,ω}^PV P_{t,ω}^PV_curtail P_{t,ω}^PV_forecast对风机同理。这个约束在目标函数中可加入弃电惩罚项比如成本系数设为很低的正数确保系统在正常情况不会主动弃电但在极端情景下允许少发以维持功率平衡。2.3 储能系统建模及其约束储能是微电网灵活性的核心。基本动态方程为SOC_{t1,ω} SOC_{t,ω} η_ch * P_{t,ω}^ch * Δt / E_rated - P_{t,ω}^dis * Δt / (η_dis * E_rated)其中ν是充电效率η_dis是放电效率E_rated是储能额定容量Δt是时间步长1h。SOC需要满足上下限约束比如SOC_min ≤ SOC_{t,ω} ≤ SOC_max充放电功率也需要限制0 ≤ P_{t,ω}^ch ≤ P_ch_max * u_{t,ω}0 ≤ P_{t,ω}^dis ≤ P_dis_max * (1 - u_{t,ω})这个u是二进制变量表示充放电状态互斥。如果加入这个变量模型就成为混合整数线性规划MILP求解难度会上升。但实际中充放电同时发生不仅不经济也损伤电池因此论文里通常会加。如果不想用二进制变量也可以把充放电功率上限设成不同的区间通过目标函数中双重成本来避免同时充放电但那是近似处理审稿人可能会挑刺。储能还有一个关键约束是调度周期始末SOC相等日前调度通常要求日循环平衡也就是SOC_{0,ω} SOC_{T,ω}如果你做的是滚动优化则不需要这个约束初始SOC由上一时段结果传递。不同的假设会显著影响结果复现时一定要先明确论文采用哪种模式。2.4 非预见性约束的引入非预见性约束Nonanticipativity Constraints是随机规划实现信息结构的核心。简单解释在日前决策阶段我们不知道明天中午到底是晴天还是阴天因此凡是需要在日前的第一阶段定下来的变量比如与大电网的日前购电合同、储能是否参与调峰对所有场景都必须取值相同。用数学表达如果一个决策变量集合x是第一阶段的那就必须满足x^ω x^{ω}, ∀ ω ≠ ω但在代码实现中我们通常不会真的添加“所有场景之间相等”这种庞大的等式组而是直接把这些变量设为无场景索引的独立变量比如我在Matlab中使用相同的列向量代表所有场景共享的购电计划。而第二阶段的变量如实际储能充放电功率、弃光量等则定义为每个场景一个副本用三维矩阵P_ch(t, scenario)来存储。这里有个容易混淆的地方储能SOC虽然是第二阶段变量但它由初始SOC和各个时段的充放电决策累加而来所以它天然是场景依赖的。而“非预见性”要求的是在每一个时间节点上决策只能利用到该时刻之前可获得的信息。对于日前调度通常将所有时段都有“完整信息”的模型称为“预见性模型”那是理论上界而实际中第一阶段变量不可依赖未来场景这就是非预见性的意义。在代码中非预见性约束如果写错会出现所谓“偷看未来”的问题。我见过不少复现代码所有变量都定义了三维矩阵却在设置第一阶段变量时不小心引用了场景索引导致每个场景的日前决策不同结果出来之后收益虚高、不可执行。测试方法很简单检查输出结果里购电/售电计划在相同时间段是否对所有场景完全一致。如果不一致说明非预见性约束没加对。3. Matlab实现步骤与代码框架3.1 工具箱选择与环境配置Matlab实现这类优化问题主要用两种方案一是调用第三方求解器比如Gurobi、CPLEX、MOSEK通过yalmip或cvx建模二是使用Matlab自带的linprog、intlinprog但性能较差场景一多就跑不动。我的环境是Matlab R2022b YALMIP Gurobi 10.0。YALMIP是一个高效的建模工具箱支持线性、整数、二次等多种约束写起来比直接用求解器API直观很多。安装步骤如下从YALMIP官网下载最新压缩包解压到D:\Toolbox\yalmip。在Matlab中设置路径addpath(genpath(D:\Toolbox\yalmip))。安装Gurobi并获得学术许可然后在Matlab里addpathGurobi的Matlab接口目录。测试是否可用在Command Window输入yalmip(clear)然后sdpvar x; optimize([] , x, sdpsettings(solver,gurobi))如果solver输出没有报错就说明环境配好了。没有Gurobi许可证时可以先用intlinprog跑小规模模型验证正确性。但一旦场景数超过20intlinprog的速度会让人怀疑人生。所以我建议申请Gurobi或CPLEX的学术授权通常一天内就能下来。3.2 场景生成与缩减技术随机规划的质量强烈依赖场景集。最简单的场景生成方式是蒙特卡洛采样假设光伏和负荷预测误差服从正态分布生成大量样本再通过同步回代缩减Scenarios Reduction筛选出具有代表性的少数场景。这一步如果省略直接用原始样本模型规模会爆炸。我在复现中采用了以下流程给定预测值P_forecast(t)误差比例系数 α比如 0.2设ε ~ N(0, α * P_forecast)。生成2000个原始场景每个场景包含24h的光伏、风电和负荷。使用k-means聚类或者调用scenarioReduction函数如果没有现成函数用kmedoids也行将这些场景缩减为10~30个代表性场景并记录每个场景的概率权重。对缩减后的场景检查功率平衡约束不能出现统计意义上的异常比如某场景光伏出力为负需要置零。场景缩减的目的是平衡精度和计算量。10个场景时模型大约有几千个变量Gurobi可以秒级求解30个场景时规模翻倍但结果精度已经非常接近大样本情形。通过绘制不同场景数下的目标函数值曲线可以看到在某个临界点后增加场景数带来的收益微乎其微此时就可以确定场景数量。3.3 鲁棒优化模型转换与求解器调用如果严格采用鲁棒优化需要将不确定参数的不确定集比如盒式集合、椭球集合代入约束并将半无限规划转换为等价的线性/二阶锥约束。例如对功率平衡约束中的光伏出力P_t,ω^PV如果其变化范围为[P_t^PV_bar - delta_PV, P_t^PV_bar delta_PV]则最坏情况是光伏最小出力、负荷最大需求同时发生鲁棒约束就变成P_t^PV_min P_t^WT_min P_t^DG P_t^dis P_t^buy ≥ P_t^load_max P_t^ch P_t^sell这个转换直接把不确定性消除为确定性优化非常容易实现。但缺点是很保守因为最坏工况几乎不可能同时发生。为了降低保守性可以引入调节参数Γ鲁棒控制参数在0到T之间。当Γ0时退化为确定性模型ΓT时退化为最保守的盒式模型。在YALMIP里这个参数可以简单加在约束的左侧或右侧通过循环添加不同常数值即可。如果论文采用“分布鲁棒”还会涉及矩不确定集合模型会变成半定规划或带有二阶锥约束的MILPYALMIP也能处理。不过复现时我建议先把确定性、随机、传统鲁棒三种方案都跑通再做扩展。3.4 非预见性约束的线性化表示在实际Matlab代码中非预见性约束通常不显式写出等式组而是通过变量的定义方式来实现。假定所有决策变量定义如下% 第一阶段变量所有场景共用 P_buy sdpvar(T, 1, full); P_sell sdpvar(T, 1, full); u_dg binvar(T, 1); % 某些机组状态也可能第一阶段决定 % 第二阶段变量场景依赖 P_ch sdpvar(T, N_scene, full); P_dis sdpvar(T, N_scene, full); SOC sdpvar(T1, N_scene, full);注意P_buy没有第二维它天然满足非预见性约束。但如果论文中第一阶段变量有多个类别比如储能充电状态和放电状态则用二进制变量u_storage(t, ω)时需要额外添加约束u_storage(t, ω) u_storage(t, ω) ∀ ω≠ω因为二进制变量若定义了场景维度就必须显示约束。还有一种更简洁的方式是利用YALMIP的repmat将第一阶段变量复制成场景维度后让所有场景共享同一个变量索引本质上是从模型层面避免重复定义。不过显示写约束更保险程序员看起来也更好懂。我踩过的一个坑是SOC约束里要确保每个场景的初始SOC相同不能写成S0 sdpvar(1,N_scene)然后用随机数赋值。正确写法是定义SOC(1, :) repmat(SOC_initial, 1, N_scene)或者直接在约束中添加SOC(1, ω) SOC_initial对所有 ω。4. 实操过程与结果分析4.1 典型日负荷与风光出力数据准备复现的第一步要准备输入数据。我用了一个基准日负荷曲线峰值为1000kW谷值为400kW光伏预测峰值为例800kW风电预测峰值300kW。所有数据都以向量形式加载到Matlab中并做了标幺化处理方便后续调整。以0.1kW为步长的连续变量和以二进制表示的机组状态变量混合使用会让模型变成MILP。数据准备阶段应尽量用稀疏矩阵格式存储场景数据特别是当场景数较多时全维度的reshape会导致内存爆炸。我习惯用[T * N_scene, 1]的向量形式构建所有变量既便于约束构造也利于求解器处理。4.2 求解流程与参数设置模型构建完成后我采用YALMIP调用Gurobi求解。sdpsettings中需要注意几个关键参数options sdpsettings(solver, gurobi, verbose, 2, showprogress, 1); options.gurobi.MIPGap 0.01; % 1%的MIP间隙 options.gurobi.TimeLimit 300; % 300秒超时 options.gurobi.NumericFocus 1;对于MILP问题MIPGap设置得太小会拖慢求解速度对于随机规划场景数量在20以内1%间隙完全够用。如果发现求解时间过长可以尝试将二进制变量的数量降下来比如把机组启停变量从逐时段改为分段线性化。求解结束后我用value()提取解并验证功率平衡约束是否被满足到数值容差内。一个常用的检查手段是计算所有时刻的功率残差向量如果残差数量级超过1e-3说明模型或数据有问题需要回去检查约束是否正确写入。4.3 结果对比确定性、随机与鲁棒方案我分别运行了三种模型得到的结果非常有代表性确定性模型把光伏/风电/负荷都设为预测值得到当天总成本最低但该计划在极端场景下储能SOC会越限购电功率也可能超过联络线限额。随机规划模型以10个缩减场景为输入目标函数是期望成本。求出的计划比确定性方案保守一些但实际可执行性大幅提高所有场景的功率平衡和SOC约束都满足。鲁棒模型Γ24即最坏情况成本最高但无论在哪个场景下系统都能在无切负荷的情况下运行适合可靠性要求极高的场景。下面用一个简化表格展示三者的典型结果对比模型类型期望运行成本元最大购电功率kW储能日循环次数是否满足所有场景约束确定性模型85006201.2否部分场景失稳随机规划10场景90206101.0是鲁棒模型最大98005700.8是从结果可以看出随机规划与鲁棒模型之间只差约8%的成本但鲁棒模型的储能调度更保守日循环数降低有利于延长电池寿命。如果你做实际工程很可能更愿意选择随机规划结果因为经济性和可靠性平衡更好。4.4 运行时间与求解性能优化初始代码可能很慢尤其是当场景数和时段数增多后。我遇到过700个二进制变量、近2万个连续变量的模型Gurobi求解耗时接近20分钟。后来做了几项优化去掉冗余约束确保每个约束都是必要的例如充放电互斥变量在某些场景下可以不引入。使用Big-M法时谨慎选择M值。如果M太大数值稳定性会变差如果M太小可能错误地切掉可行域。我通过先求解一个松弛模型得到变量的上界再取1.1倍作为Big-M值。利用分段线性近似替代非线性项比如把储能退化成本中的平方项近似为分段线性函数不仅保持线性模型误差也可控。采用分解算法如果场景规模实在太大可以用Benders分解或拉格朗日松弛将场景子问题解耦。这部分工作量大但对理解算法很有帮助。实测下来10场景模型在当前配置下只需3~5秒即可求解完成而50场景模型可能需要几分钟。因此如果没有特殊要求建议将场景数设置在20以内。5. 常见问题与排查技巧实录5.1 求解器报错infeasible怎么办整个复现过程中我最常遇到的问题就是模型无解。遇到“infeasible”时不要慌按以下步骤排查检查功率平衡约束中的单位是否一致。kW与MW混用是初学者最爱踩的坑。检查储能SOC初始值和终值约束是否与容量匹配。如果SOC范围设置为[0.2, 0.9]那么初始和结束值就必须落在这个范围内同时充放电功率乘以时间步长后不应导致SOC越界。检查二进制变量数目是否过多。有时模型本身可行但由于数值问题被求解器误判为不可行可以适当提高NumericFocus或者调整Big-M。使用YALMIP的assign和check检查约束残差定位是哪个约束导致不可行。YALMIP里有个小技巧diagnostics optimize(constraints, objective, options); if diagnostics.problem 1 % infeasible % 可以尝试注释部分约束二分查找问题来源 end最实用的方法是分模块调试先不加入储能方程只做功率平衡能求解后再加上储能约束最后加入非预见性约束。这样很快能定位哪一步导致不可行。5.2 非预见性约束写法错误导致维度爆炸有读者问我为什么他写的非预见性约束总是让模型变量数量翻倍。原因通常在于他把第一阶段变量定义成了sdpvar(T, N_scene)然后又写了for i1:N_scene, for j1:N_scene, constraint [constraint, x(:,i) x(:,j)]瞬间增加了N*N条约束。其实这部分约束可以大幅简化不需要两两比较只需要令所有场景的变量等于第一个场景x(:, ω) x(:, 1), ∀ ω 2,...,N_scene但更优雅的做法是压根不去定义场景维度的第一阶段变量。直接在建模时就把购电计划定义为T维向量然后在场景约束中引用这个公共变量。这样既减少变量数又天然满足非预见性还减少约束个数Gurobi跑起来更快。另外要注意非预见性约束的处理范围。如果论文是做多阶段的比如日内滚动那么在第一阶段和第二阶段的连接处也需要引入类似的非预见性约束这时就必须在模型中显式定义场景树。代码实现时建议先画清楚场景树结构不要凭感觉写。5.3 场景数目与计算时间的权衡有些项目把场景数设为500然后抱怨求解时间太长。这其实是没有必要。随机规划的价值在于用少量代表性场景逼近真实分布而非把所有历史样本都扔进模型。参考经验10个场景基本能捕获90%以上的不确定性影响30个场景时结果趋于平稳超过50个场景目标函数值的变化通常小于0.5%。所以建议起步用5个场景试跑确认模型完全正确后再扩到20个左右。我在一次复现中从100场景缩减到15场景结果目标函数差异只有1.2%但求解时间从小时级降到了分钟级这个性价比非常高。如果确实需要大量场景可以采用逐步对冲算法或Benders分解将场景子问题并行化。不过那是进阶玩法论文复现阶段不必一步到位。5.4 储能SOC初值设置技巧与陷阱储能SOC的初值对日前调度结果影响非常大。有些论文假设调度周期结束后SOC回到初始值那么初值选择不影响结果但会决定储能在全天的充放电节奏。如果初值设置过高储能会偏向放电设置过低则偏向充电。复现时最好把初始SOC设为论文提供的值或者设为SOC中位数。另外要特别小心“SOC的变量顺序”如果你定义的是SOC(t1) SOC(t) ...则在约束构造时要注意场景维度索引是否正确。我曾经在循环里把SOC(t, ω)写成了SOC(t1, ω)导致所有SOC约束错位求解器报无解排查了两个多小时才发现是数组下标错位。建议初始化时先打印一下变量的尺寸并设置SOC(1, :) initial_SOC后单独跑一个不带储能的模型验证再做全模型求解。6. 复现心得与进一步扩展建议这篇文章把我复现“含可再生能源与储能的区域微电网最优运行”的完整流程写了一遍。开头我提到很多人在复现SCI论文时只盯着公式忽略了信息结构和非预见性约束导致最终代码运行结果虽然漂亮生产出来的计划却根本无法执行。实际上一个真正可靠的微电网调度模型核心不只是数学推导而是对“决策时序”和“不确定性边界”的严谨建模。说几个我个人在实际操作中的体会。第一不要一开始就追求完美复现论文的每一个细节先跑通一个简化版本再逐步添加复杂性。第二非预见性约束是衡量你真正理解随机规划与否的分水岭它往往占不到代码总量的10%却决定了结果能否经得起推敲。第三储能模型不是变量越多越精确要时刻关注求解性能因为科研需要的是可比较、可复现的结果而不是一个跑三天也出不来的“黑箱”。如果后续要扩展可以从几个方向入手把单目标经济调度扩展为多目标经济碳排可靠性采用NSGA-II这类启发式算法做帕累托前沿分析把日前调度扩展到日内滚动调度对比不同预测更新频率对运行成本的影响或者把储能寿命模型中加入SOC循环老化的非线性函数用分段线性化处理。这些方向论文都能用上也能体现出扎实的建模功底。最后再分享一个小技巧复现代码时别忘了把随机种子固定这样每次运行得到完全一样的结果方便写报告。Matlab里就用rng(2024)这行命令虽然不起眼但能让你的实验结果具有可重现性。希望这篇文章能帮大家少走弯路把更多的精力放在分析结果和写论文上而不是无休止地debug。
返回列表