简介:一份面向虚拟电厂/微网日前随机优化调度的 Matlab 源程序,对应知网论文《计及源荷双重不确定性的虚拟电厂微网日前随机优化调度》,适合电力专业毕业设计、课题复现及科研入门者。程序以光伏出力与负荷功率的双重不确定性为建模核心,采用随机规划处理不确定变量:先基于蒙特卡洛算法对预测光伏与负荷曲线生成多个场景,再利用概率距离快速消除法削减至5个典型场景,最后对多场景下的虚拟电厂调度策略进行优化,兼顾源荷不确定性与运行经济性。压缩包为rar格式、大小约3.66MB,内部文件构成明细暂未提供,下载后可按需解压查看主程序与相关文档。目前已有87人学习/下载。代码结构清晰、注释完整,可直接运行并对照论文关键公式理解建模思路,也可在此基础上扩展为交直流混合配电网、多阶段随机优化等更复杂调度模型,是一份实用性较强的参考实现。
1. 计及源荷双重不确定性的虚拟电厂日前随机优化调度:这套资源把随机规划讲透了
做虚拟电厂或微网日前调度的人,迟早会撞上同一个问题:光伏出力和负荷功率都不是一个确定数。你今天按中午 100 kW 的光伏来排机组和储能,明天可能只有 60 kW,负荷也来回跳。这套 141 号资源解决的就是这个痛点——先把预测曲线和误差分布用蒙特卡洛生成一堆场景,再用快概率距离消除法削成 5 个代表性场景,最后在这个场景集上跑随机优化调度,得到的是“综合考虑各种可能情况后期望成本最低”的计划,而不是某个单一预测值下的“最优”。它适合两类人:做毕业设计要写完整调度模型的学生,以及想从确定性优化切换到场景法却摸不着门路的工程师。下面把建模思路、参数设置和踩过的坑一次说清。
2. 为什么用随机规划:双重不确定性下的日前调度建模思路
2.1 确定性调度的局限性
常见的日前调度程序,通常把光伏出力和负荷功率当成已知输入,直接代入约束求解。这种做法的前提是预测足够准,实际中却很难成立。光伏预测误差跟云量、温度、空气质量都相关,夏季午间最大偏差可能到 20% 以上;负荷预测则受工作日、节假日、极端天气影响。如果只按预测均值排计划,当真实光照偏低时,系统可能缺电需要切负荷,或者储能被迫超充;真实负荷偏高时,又可能因备用不足导致运行成本飙升。
随机规划的思路不是去消灭误差,而是把这个误差显式地建进模型。它假定光伏和负荷服从某种概率分布,用抽样生成的可能场景去覆盖不确定区间,最终求出一个“在所有场景下平均表现最优”的调度方案。相比鲁棒优化追求最坏情况下的可行,随机规划得到的结果更贴近经济性目标,计算代价也可控。这套资源里用的正是随机规划法,配合蒙特卡洛场景生成和场景削减,属于工业界和学术界都认可的标准做法。
2.2 两阶段决策结构:日前计划与实时调整
虚拟电厂日前随机调度本质上是一个两阶段随机规划问题。第一阶段是“现在必须做决定”的变量,包括机组启停、储能充放电计划、与上级电网的交互功率计划等。这些变量在观测到真实光伏和负荷之前就要定下来,所以叫非预期变量。第二阶段是“看到某个具体场景后才调整”的变量,比如出力偏差、切负荷量、弃光量、备用调用量等,它们是场景依赖的。
目标函数写成所有场景下总成本的期望值,而不是单个场景的成本。这样做的一个直接好处是:优化器会自动在“按预测值制定的计划”和“应对偏差的调整手段”之间找平衡。比如光伏预测较高时,它不会把备用压得太低,否则某些场景下要买昂贵的实时电力;也不会把备用留太多,否则日常成本过高。这种权衡正是随机优化的精华。
2.3 概率分布假设与误差建模
程序里处理光伏和负荷两种不确定性的基础,是它们的预测误差分布。常见做法是假设误差服从正态分布,即实际值 = 预测值 + 零均值、给定标准差的正态随机量。标准差可以选择与预测值成正比的相对误差,也可以按历史数据统计得到。例如光伏出力在正午波动大,可以用 15% 的相对误差;负荷夜间相对平稳,用 5% 更合适。
这套资源的程序里,场景生成阶段直接对预测曲线加噪声。生成 1000 个初始场景时,每个场景都是一条完整 24 小时的光伏或负荷曲线。注意这里不能各时段独立抽样,否则场景之间的时序平滑性很差,削减后的代表性也不好。我一般会在抽样后加一个滑动平均预处理,或者用 AR(1) 模型生成误差时间序列,让相邻时刻的出力曲线更接近真实物理过程。程序跑出来的调度结果是否合理,相当一部分取决于这一步你的分布参数给得对不对。
3. 场景生成与削减:蒙特卡洛怎么一步步走向 5 个代表场景
3.1 蒙特卡洛抽样生成初始场景集
场景生成的目标是为不确定性变量构造一批“模拟可能发生的情况”。蒙特卡洛抽样的核心是大量随机采样,用统计规律逼近真实分布。对光伏出力场景,先定义一个 24 维均值为预测曲线的正态分布向量,协方差矩阵可以简化成对角阵,也可以加一个时间相关性系数。代码如下:
% 场景生成:基于预测曲线 + 误差分布 N_scen = 1000; % 初始场景数 T = 24; % 调度时段数 mu_pv = P_pv_forecast; % 光伏预测曲线 (1x24) std_pv = 0.15 * mu_pv; % 标准差:15%相对误差 mu_load = P_load_forecast; % 负荷预测曲线 (1x24) std_load = 0.05 * mu_load; % 标准差:5%相对误差 % 生成光伏场景矩阵,每行一个场景 pv_scen = repmat(mu_pv, N_scen, 1) + randn(N_scen, T) .* repmat(std_pv, N_scen, 1); % 生成负荷场景矩阵 load_scen = repmat(mu_load, N_scen, 1) + randn(N_scen, T) .* repmat(std_load, N_scen, 1);逻辑说明:repmat把预测曲线复制成 N 行,加上随机噪声后就得到 N 条完整曲线。randn(N_scen, T)生成标准正态随机矩阵,相乘后逐元素加到预测曲线上。参数说明里最值得盯的是std的选法:它决定了生成的场景向真实值的偏离程度。如果设太小,场景间差异小,削减后体现不出不确定性;设太大,边界场景过于极端,调度结果偏向保守。我一般先用 10%~15% 跑一遍,再根据削减后场景的分布微调。
3.2 快概率距离快速消除法:原理与实现
生成 1000 个场景直接放进优化模型,决策变量会膨胀到难以求解的程度。这时需要场景削减。快概率距离快速消除法的思想很简单:
- 把每个场景看作一个多维向量,定义两两之间的距离(一般用欧氏距离或加权欧氏距离)。
- 计算每个场景被删除后,相对保留场景集带来的概率距离变化。
- 每次删掉一个变化最小的场景,并把它的概率累加到离它最近的保留场景上。
- 重复直到场景数降到目标值。
代码示意:
% 快概率距离场景削减 target = 5; % 目标场景数 p = ones(N_scen, 1) / N_scen; % 初始各场景概率均等 while size(pv_scen, 1) > target % 计算所有场景两两间的距离矩阵 D = pdist2(pv_scen, pv_scen); % 也可对负荷联合场景求距离 % 对每个场景找到最近邻及距离 D(logical(eye(size(D)))) = inf; [dist_min, idx_near] = min(D, [], 2); % 计算每个场景被删除后带来的概率距离损失 loss = p .* dist_min; % 找损失最小的场景,融合概率后删除 [~, idx_del] = min(loss); p(idx_near(idx_del)) = p(idx_near(idx_del)) + p(idx_del); p(idx_del) = []; pv_scen(idx_del, :) = []; load_scen(idx_del, :) = []; end这里做的是同步削减,也就是光伏场景和负荷场景配对削减:每个初始场景同时包含一条光伏曲线和一条负荷曲线,距离计算取两者偏差的加权和。我一般用光伏距离权重 0.5、负荷距离权重 0.5,因为两者对调度目标的影响都很直接。如果想突出最关心的变量,可以给那个维度更高的权重。距离函数的选取会影响削减结果,欧氏距离计算的是整体趋势贴近程度;如果要保留尖峰和低谷特征,也可以用基于动态时间弯曲的相似度,但计算量会大一个量级。这个程序用欧氏距离是合理的,快且稳定。
3.3 削减到 5 个场景:为什么够用,概率怎么分配
不是场景越少越好,也不是越多越准。5 个场景是简化模型与求解精度的一个常见折中。削减后每个场景自带一个概率值,表示该场景在原始分布中的代表性权重。优化模型中,目标函数是这些场景期望成本的加权和,所以场景概率分配直接决定了计划偏向于哪些可能情况。
检查削减效果有个直观方法:计算削减前 1000 个场景的均值曲线与削减后 5 个场景的加权均值曲线,两者应基本重合,误差不会超过几个百分点。如果偏差大,说明初始场景生成或距离度量有问题。另外,还要确认削减后的场景集里,最坏情况和最好情况没有都被削掉。如果 5 个场景全是中规中矩的曲线,那随机优化的意义就大打折扣了。
4. 多场景随机调度程序实现:目标函数、约束与程序结构
4.1 目标函数怎么设:期望成本最小化
随机调度的目标函数要根据虚拟电厂内部的设备构成来确定。典型构成有燃气轮机、储能、光伏、以及可在一定范围内交换功率的上级电网接口。程序里除去光伏和负荷的不确定性外,调度变量主要是机组出力、储能充放电、从电网购售电。目标函数写为各场景概率加权的总运行成本期望:
min sum_s p_s * [ sum_t ( C_fuel(P_g,s,t) + C_gridbuy(P_buy,s,t) - C_sell(P_sell,s,t) ) ]其中p_s是场景概率,C_fuel为燃料成本,P_buy、P_sell为购售电功率。如果考虑碳排放,可以再加一个排放惩罚项。随机规划的目标函数与确定性模型的区别,只是把单场景成本换成了多场景期望,约束部分则需要区分第一阶段和第二阶段变量。
在 YALMIP 里的写法大致如下:
% 成本系数与决策变量定义 P_g = sdpvar(T, N_scen); % 机组出力,场景依赖 P_b = sdpvar(T, N_scen); % 购电功率 P_s = sdpvar(T, N_scen); % 售电功率 E_s = sdpvar(T+1, N_scen); % 储能能量状态 % 目标函数:场景概率加权期望成本 Objective = 0; for s = 1:N_scen Objective = Objective + p(s) * sum( ... c_gas * P_g(:,s) + c_buy * P_b(:,s) - c_sell * P_s(:,s) ); end逻辑说明:这里把每个场景的时序决策都独立建出来了。注意第一阶段变量(比如机组是否启停)应该对所有场景取相同值,程序中一般直接定义为不依赖场景的变量;而第二阶段变量按(:,s)索引场景。如果第一阶段变量错误地按场景区分,优化器会在每个场景里各排一套计划,失去随机规划的本来意义。
4.2 约束条件的代码落地
随机调度的约束比普通调度多一层“非预期性”要求。核心约束包括功率平衡约束、机组出力上下限、爬坡约束、储能能量递推约束、充放电功率限制、备用约束等。功率平衡约束必须逐场景写成等式:
Constraints = []; for s = 1:N_scen Constraints = [Constraints, ... P_g(:,s) + P_pv(:,s) + P_dis(:,s) - P_ch(:,s) + P_b(:,s) - P_s(:,s) ... == P_load(:,s)]; end储能约束则要注意跨场景的一致性:初始能量是固定的,终止能量可以自由但一般设一个范围,避免期末耗尽。如果储能变量也是场景依赖的,那么初始能量 E(1, s) 对每个场景都要一样,这是物理上必须满足的。很多程序跑出来储能充放电曲线在场景间完全不一致,就是因为这个约束没写对。
机组爬坡约束在随机规划里要检查所有场景的连续性。可以将同一场景相邻时段出力做差限制在爬坡范围内。这里有个细节:如果机组在 0 和最小技术出力之间跳变,需要引入二进制启停变量,否则约束失效。这类混合整数问题在 5 个场景、24 时段规模下,求解器通常几分钟内能收敛;场景多了就会明显变慢。
4.3 程序分层结构与参数传递
整套程序我习惯分成四个模块:数据输入、场景生成与削减、模型构建、求解与结果输出。数据输入模块放预测曲线、设备参数、电价曲线、储能参数;场景生成模块输出削减后的场景集和概率;模型构建模块负责把变量和约束拼进一个Constraints结构;求解模块用optimize(Constraints, Objective)求解,并取出结果画图。
一个容易翻车的地方是变量维度对不上。场景削减后场景数是 5,但负荷数组、光伏数组、概率数组都要同步重排;如果削减代码只返回了场景却忘了返回概率,目标函数权重就会恒等于 0。我建议在主程序开头把变量维度全部打印出来核对一遍:
fprintf('场景数: %d, 时段数: %d\n', N_scen, T); fprintf('pv_scen: %dx%d, load_scen: %dx%d\n', size(pv_scen,1), size(pv_scen,2), ... size(load_scen,1), size(load_scen,2)); fprintf('场景概率之和: %.6f\n', sum(p));概率之和不等于 1 是最常见的问题,因为削减过程中删场景时融合概率的索引可能指错。每删除一个场景,被删场景的概率要加给它的最近邻,但最近邻索引必须在删除前的原矩阵中取;一旦你先删了行,再拿索引去取值就会越界或错位。程序里一定要保留原索引映射表。这类问题排查起来很隐蔽,看起来是数组大小错误,实际是削减逻辑顺序问题。
4.4 结果怎么解读:期望成本与场景内差异
求解完成后,除了总的期望成本,还要看每个场景下的具体计划。程序会在每个场景中展示光伏消纳、储能充放电、购售电情况。如果某个场景光伏高、负荷低,储能应该在这一天多充电,机组少出力甚至停机;光伏低、负荷高时,储能放电,机组满发,必要时购电。只有场景间的计划体现出这种差异化调整,随机优化才算跑通。如果所有场景给出的计划几乎一样,就要回头查场景削减是不是把差异都削没了,或者目标函数权重设置不当导致优化器根本不在乎场景差异。
5. 避坑与常见问题:跑通这套随机调度程序的三个关键坑
5.1 削减后场景概率之和不等于 1
现象:目标函数计算出来的期望成本明显偏大或偏小,查看场景概率数组发现 sum(p) 不是 1,有时是 0.99,有时是 1.05。
原因:场景削减循环中,删除场景后最近邻索引没有同步更新。假设原来有 1000 个场景,删除第 800 个后,第 801 个往后整体前移一位;但程序里还拿着原来的索引去访问数组,结果把概率加到了错误的场景上,甚至导致越界。
解决:不要在原数组上边删边操作,而是维护一个active_idx索引表。每次需要删除时先记录要删的原始序号,然后找到它的最近邻原始序号,做概率累加后再更新索引表。最后拿索引表去索引场景矩阵。
active_idx = 1:N_scen; while length(active_idx) > target % 用当前活跃场景计算距离矩阵 sub_pv = pv_scen(active_idx, :); sub_load = load_scen(active_idx, :); D = pdist2(sub_pv, sub_pv) + pdist2(sub_load, sub_load); D(logical(eye(size(D)))) = inf; [dist_min, idx_near] = min(D, [], 2); loss = p(active_idx) .* dist_min; [~, pos_del] = min(loss); p(active_idx(idx_near(pos_del))) = ... p(active_idx(idx_near(pos_del))) + p(active_idx(pos_del)); active_idx(pos_del) = []; end这段代码把索引管理显式化,删的是active_idx里的位置,但概率累加用的是原始场景编号指向的概率,不会错位。
5.2 储能初始能量在场景间不一致导致约束矛盾
现象:求解器报模型不可行(infeasible),或者储能出力曲线出现不合理的跳变。
原因:储能能量状态变量E(1, s)如果是场景依赖的,且不同场景初始能量不同,那么第一阶段计划没法统一。比如场景 1 初始能量高,可以少充电多放电;场景 2 初始能量低,需要多充电,但充电功率计划在第一阶段已经限定死了,冲突就产生了。
解决:把储能初始能量设成固定值,不按场景索引;或者在约束里加E(1, :) == E_init,强制所有场景的初始状态一致。终止能量可以留自由度,但建议加一个范围约束,避免程序为了省钱把储能最后一刻全部放完。这样既保证物理一致性,又不会过度限制优化空间。
5.3 场景数一多求解时间成倍暴涨
现象:初始 1000 个场景削减到 10 个,求解时间从几秒涨到几十分钟;减到 15 个现场机直接内存溢出。
原因:随机规划模型的复杂度与场景数成正比,甚至更差。每个场景都要复制一套决策变量和约束,二进制变量也随之翻倍。求解器在做分支定界时,搜索树的规模增长远超线性。
解决:先用 3 个场景跑通全流程,确认约束和参数没有问题再上 5 个场景。如果必须处理更多场景,考虑把二进制启停变量在第一阶段固定,也就是先求解一个确定性计划确定启停序列,再在固定启停的条件下对多场景做机组出力和储能的优化。这样会把问题变成连续优化,求解速度会快一个数量级。代价是启停序列不是全局最优,但工程上够用。
6. 随机调度结果的一个进阶验证技巧:场景期望值回溯检验
拿到优化结果后,先别急着写论文。我习惯做一个回溯检验:用削减后的 5 个场景分别回放调度计划,算出每个场景下的实际成本,然后再拿未削减的 1000 个原始场景数据做一次蒙特卡洛模拟,把模拟结果的平均成本和模型给出的期望成本对比。两者偏差在 3% 以内,说明场景削减损失的信息量可控;如果偏差超过 5%,大概率是削减距离权重设置有问题,或者 5 个场景太少,需要退回 8~10 个场景重新跑。
实现回溯检验不复杂。把优化得到的计划变量取出,固定住,然后在每个原始场景中直接计算功率平衡偏差。如果某条原始场景下出现失负荷或弃光,记下对应的惩罚成本,最后求平均。这一步本质上是在检验计划对“没见过的场景”是否鲁棒。随机优化的目标函数只保证在削减场景集上期望最优,但削减后的场景毕竟是原始分布的近似,必要时得验证这个近似的充分性。
除了期望值,我还会对比各场景下储能和机组出力曲线的离散程度。正常计划应该是:光伏高的场景储能多充电、机组少出力;光伏低的场景储能多放电、机组加出力。如果所有场景的蓄电池曲线几乎重叠,说明场景削减把不确定性削没了,程序变成在重复求解同一个确定性模型。判断这个只需画一张各场景储能功率曲线图,直观比任何指标都管用。
从那次被存储初始能量不一致绊住后,我每次跑随机调度程序都会强制走一遍三段式检查:场景概率之和为 1、各场景初始状态一致、削减后场景均值与原始均值偏差小于 3%。这三步做完,程序才敢真正拿去改参数跑工况。希望这份拆解帮你在做虚拟电厂或微网调度的时候少走几个弯路。
本文还有配套的精品资源,点击获取