ARTICLE DETAIL

资讯详情

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

MATLAB+CPLEX实现虚拟电厂随机调度:源荷双重不确定性建模

MATLAB+CPLEX实现虚拟电厂随机调度:源荷双重不确定性建模 干微网和虚拟电厂调度这几年我越来越觉得确定性调度模型是个“精致的谎言”。光伏预测误差动辄百分之二三十负荷预测在午晚高峰也是说翻脸就翻脸可传统模型偏偏把这两个数当成板上钉钉的已知参数一口气把所有时段的机组出力和储能策略全定死了。结果就是计划在纸面上完美实际运行里不是弃光就是缺电最后全靠人工兜底。这篇文章想聊的就是用MATLABCPLEX实现一个把光伏和负荷双侧不确定性都纳入优化框架的虚拟电厂随机调度模型包含场景生成、削减、建模到求解的全流程。后面你看到的代码都是我自己在项目里跑过的写法模型规模不大但思路可以直接扩展到几十台分布式电源的场景。1. 确定性调度在微网场景里为什么总失守1.1 常规模型“看着正确用着翻车”的三个典型场景先说一个最常见的翻车场景夏季午间光伏预测给到1000kW模型据此安排储能中午放空、燃气轮机压低出力打算用光伏扛过午高峰。结果云层一来实际出力只有800kW储能又已经放空系统只能从电网高价购电购电上限还卡着最后被迫弃负荷。这个问题不在预测算法而在模型结构——它根本不认为“预测会错”。第二个场景是晚高峰负荷跳变。负荷预测一般误差在3%以内听起来不大但燃气轮机爬坡速率有限如果晚高峰负荷比预测高出一截机组在15分钟内爬不到目标出力系统频率和电压就会很难看。确定性模型里没有“爬坡失败”这个概念它默认预测值一定发生因此不会在更早的时段提前抬高基载出力或留储能力。第三个场景更隐蔽光伏和负荷的误差如果同向叠加比如预测都乐观了实际光伏少、负荷多两个不确定性同时朝不利方向偏移备用容量看似足够实际被瞬间打穿。这也是为什么很多微网调度系统在线运行一年半载后运营人员会偷偷把预测值乘以一个0.9的折扣系数——本质上就是在用手工方式补偿确定性模型的缺陷。与其这么干不如把不确定性本身建模进去。1.2 不确定性不是“误差”而是新增的决策维度做调度的朋友容易陷入一个思维惯性预测不准那我就把预测模型调准一点。这个方向没错但永远也调不到100%准。随机调度的思路是换一个角度——既然预测结果一定带随机性那就在优化模型里显式表达这种随机性让目标函数和约束条件都建立在“多种可能发生的情景”之上。通俗点说确定性调度像是在导航软件里假设“一路绿灯、没有堵车”给你一条最快路线随机调度则是同时考虑100种路况每条路况下都算一遍到达时间和油耗最后找一条“平均表现最优且在最坏情况下也不至于太离谱”的路线。代价是计算量变大换来的是决策对不同场景的适应能力。所以源荷双重不确定真正改变的不是预测精度而是优化问题的结构原来是一个单点预测下的确定性问题现在变成了一组场景下的随机规划问题。这也是为什么很多教材里把随机调度称为“带概率权重的多场景优化”——它把不确定性从数据层面提升到了模型层面。1.3 三条技术路线场景法、鲁棒优化和分布鲁棒工程上为什么首选场景法处理不确定性有三条主流路线我建议刚入行的朋友先把场景法跑通再根据项目需要去接触另外两种。方法不确定性处理方式目标函数保守度求解工具成熟度适用场景场景法随机规划用有限场景离散概率分布期望成本最小化中等成熟CPLEX可直接求解MILP微网、虚拟电厂、电力市场鲁棒优化用不确定集合描述所有可能取值最坏情况成本最小化高较成熟但需线性化技巧保供电、安全校核分布鲁棒用模糊集刻画分布不确定性最坏分布下的期望成本介于两者之间研究阶段为主高阶进阶、论文课题工程落地我基本默认场景法。第一个理由是模型最终会转化为MILP或MIQPCPLEX、Gurobi这类商业求解器非常成熟不需要自己写算法。第二个理由是场景法可以透明地控制“考虑了多少不确定性”——场景数少时偏乐观场景数多时偏保守调参逻辑清晰。第三个理由是结果容易解释向项目方汇报时可以说“系统在50种典型光伏和负荷情景下都满足约束”比晦涩的对偶变量直观得多。2. 虚拟电厂随机调度模型的数学骨架2.1 先把虚拟电厂的家底列清楚建模第一步不是写公式而是把系统里的元件按“可控/不可控”和“确定/不确定”两个维度分类。我习惯建一张表表里写清楚每个元件的角色。元件类型建模方式在随机调度中的角色光伏不可控、不确定场景参数每场景一条出力曲线不确定性来源之一常规负荷不可控、不确定场景参数每场景一条负荷曲线不确定性来源之二燃气轮机可控优化变量出力、启停平衡手段受爬坡约束储能可控优化变量充放电功率、SOC跨时段调节手段电网关口可控优化变量购电、售电与外部的交换通道受上限约束注意虚拟电厂和微网在数学模型上本质是一回事都是在一个可控边界内聚合了分布式电源、储能和负荷对外跟电网交互。下面我统一用“虚拟电厂”这个词代码里的算例规模其实就是一套典型微网的配置。2.2 目标函数期望运行成本到底怎么算随机调度模型最常见的表达是最小化所有场景下的期望运行成本。设场景集合为Ω场景s的概率为π_s时段为t目标函数写成min Σ_{s∈Ω} π_s · Σ_{t1..T} [ c_gt·Pgt(t,s) c_buy(t)·Pbuy(t,s) − c_sell(t)·Psell(t,s) c_ess·(Pch(t,s) Pdis(t,s)) c_curtail·(Ppv_avail(t,s) − Ppv_use(t,s)) ]翻译成大白话每个场景下系统的运行成本包括燃气轮机燃料成本、从电网购电的成本售电是负成本、储能充放电的损耗惩罚以及光伏弃光惩罚。把所有场景的成本按概率加权求和得到“期望成本”。这里有两个细节值得说明。第一弃光惩罚这一项不是可有可无的它是为了让模型在极端场景下优先弃光而不是切负荷。如果不加惩罚模型可能为了满足功率平衡而把光伏出力压到任意低这在物理上不合理。第二c_ess是充放电损耗的等效成本数值不需要太大通常取燃料成本的5%~10%目的是让储能不要频繁无意义地充放电同时避免SOC轨迹出现锯齿状波动。2.3 约束条件每个场景下都成立的约束才算数随机规划与确定性规划最大的区别在于约束要逐一在每个场景下成立。核心约束有四组。第一组是功率平衡约束这是任何调度模型的灵魂Pbuy(t,s) − Psell(t,s) Ppv_use(t,s) Pgt(t,s) Pdis(t,s) − Pch(t,s) Pload(t,s)对每个时段t、每个场景s都必须满足。这条约束把光伏、负荷、燃气轮机、储能和电网关口全部绑在一起。第二组是燃气轮机约束包括出力上下限和爬坡约束Pgt_min·u_gt(t) ≤ Pgt(t,s) ≤ Pgt_max·u_gt(t) −R_down ≤ Pgt(t1,s) − Pgt(t,s) ≤ R_up这里u_gt(t)是0/1变量代表燃气轮机t时段是否运行。爬坡约束只在场景内部起作用也就是说每个场景的调度轨迹必须自身满足爬坡能力。第三组是储能系统约束包括SOC递推、容量边界和充放电互斥E(t1,s) E(t,s) η_ch·Pch(t,s)·Δt_can − Pdis(t,s)/η_dis·Δt_can E_min ≤ E(t,s) ≤ E_max E(1,s) E_initE(T1,s) ≥ E_init Pch(t,s) ≤ M·u_ch(t,s)Pdis(t,s) ≤ M·(1 − u_ch(t,s))Δt_can是我习惯写的时段长度如果时间粒度为1小时Δt_can1可以直接省略。互斥约束里的M是Big-M取储能最大充放电功率的1.2倍即可不宜取太大。第四组是电网关口约束购售电不能同时发生且都受上限约束0 ≤ Pbuy(t,s) ≤ Pbuy_max 0 ≤ Psell(t,s) ≤ Psell_max严格来说还应该加一个购售电互斥约束Pbuy·Psell0但在电价非负的常规假设下目标函数会自动抑制“同时买和卖”这种荒谬解我在代码里就没有额外加二进制变量。2.4 两阶段结构哪些决策今天拍板哪些随机应变刚开始学随机规划的工程师最容易忽略非预期性约束。简单说有些决策在“看到真实场景之前”就要定下来比如燃气轮机的启停计划、是否与电网签订购电协议而有些决策可以等“真实场景揭晓”后再调整比如燃气轮机的出力大小、储能的充放电功率。对应到代码里第一阶段变量用一维向量表示不随场景变化例如u_gt是T×1的二进制变量第二阶段变量用二维矩阵表示例如Pgt、Pbuy、Pch都是T×S的矩阵。这样做的含义是机组开停计划在日前就拍板了但具体出力可以看天吃饭。如果你把第一阶段变量也写成T×S矩阵那就等于是“全知视角”下的调度等于每个场景都有自己独立的开停计划结果会偏乐观。我在下面的代码示例里采用的是这套定义方式这也是随机规划落地中最容易出现却最不该出错的地方。3. 不确定性建模光伏和负荷场景怎么生成、怎么削减3.1 光伏和负荷到底该用什么概率分布场景法的前提是有一套合理的场景集。光伏出力的经典做法是用Beta分布拟合历史标幺值出力。Beta分布定义在[0,1]区间正好匹配光伏出力的标幺范围形状由α和β两个参数控制。实际使用时我会按时间段分别拟合比如上午10点的光伏出力和下午2点的出力分布参数就不一样这样能保留日内的变化规律。负荷预测误差通常假设服从正态分布均值取预测值本身标准差取历史预测误差的标准差。再强调一点不要机械套用正态分布。我会把历史误差画个直方图看一下如果尾巴特别长或明显偏态就要改用t分布或者分段拟合。虽然这会增加工作量但场景质量直接决定调度结果的可靠性省这一步容易翻车。光伏和负荷之间的相关性不能忽略。同一区域的分布式光伏在云层移动时出力会高度相关负荷与温度、用电习惯也有关联。处理相关性的简便方法是我在文章里反复强调的“联合抽样”而不是分别抽样再到模型里组合后面代码会演示。3.2 拉丁超立方抽样用更少的样本覆盖更全的分布抽1000次蒙特卡洛得到的结果虽然收敛但样本量太大会让后续场景削减和优化求解都变慢。拉丁超立方抽样LHS是工程上的好选择它把每个输入变量的分布分成N等份强制每个区间都有样本点因此用较少的样本就能覆盖分布的尾部。MATLAB里实现方式不复杂。先用lhsdesign生成[0,1]区间的分层均匀样本再用各分布的反函数把它转换到目标分布T 24; % 时段数 N 500; % 初始抽样场景数 rng(2024); % 负荷预测误差正态分布均值0标准差取预测值的5% u_load lhsdesign(N, T); load_err norminv(u_load, 0, 0.05); load_scen repmat(load_pred, N, 1) .* (1 load_err); % 光伏出力乘子Beta分布α2β5映射到0.1~1.0区间 u_pv lhsdesign(N, T); beta_val betainv(u_pv, 2, 5); pv_scen repmat(pv_pred, N, 1) .* (0.1 0.9 .* beta_val);其中load_pred和pv_pred是1×24的预测曲线load_scen和pv_scen是500×24的矩阵每一行就是一条完整的日曲线。如果你有历史误差数据不要硬套参数用fitdist去拟合误差分布再把反函数换成对应分布的逆CDF。3.3 场景削减K-means还是快速前向选择抽样500条曲线直接扔进优化模型变量规模和约束规模会非常夸张而且相邻曲线高度相似对求解质量没有本质帮助。所以要把500个场景削减成30~50个代表场景。这里有一条重要经验削减过程必须把光伏和负荷拼在一起联合削减。如果把光伏和负荷分别削减再组合等于默认两者独立相关性信息全丢了。正确做法是把每条曲线的光伏部分和负荷部分拼接成一个48维向量对这个大向量做聚类。% 联合削减将光伏和负荷场景拼接后聚类 X [pv_scen, load_scen]; % 500行48列 [idx, C] kmeans(X, S, Distance, sqeuclidean, Replicates, 10); prob histcounts(idx, S) / N; % 代表场景概率 prob prob(:); % 拉成1×S行向量 pv_rep C(:, 1:T); % 削减后的光伏场景 S×T load_rep C(:, T1:2*T); % 削减后的负荷场景 S×TK-means和快速前向选择Fast Forward Selection是两条主流路线我给一张对比表方法基本思想精度速度实现难度K-means按距离聚类取聚类中心对聚类数敏感快适合大样本低MATLAB一行调用快速前向选择逐步选代表性场景使概率距离最小理论精度更高慢样本大时吃力高需要写迭代逻辑微网级别的项目我推荐K-means一是因为样本规模也就几百到几千kmeans足够二是因为Replicates参数能缓解随机初始化的影响结果稳定三是跟团队其他成员沟通时大家都懂聚类。快速前向选择更适合研究场景削减理论或者样本量特别大的场合。削减后的场景概率一定要重新归一化因为聚类后每个簇的样本数占总样本的比例才是代表场景的概率手算概率时出过几次低级错误后面有专门讲。4. MATLABCPLEX环境搭建版本坑位与建模选型4.1 建模工具箱怎么选YALMIP、optimproblem还是直接CPLEX接口在MATLAB里用CPLEX有三种常见的打开方式YALMIP工具箱、MATLAB自带的优化问题建模框架optimproblem、以及直接调用CPLEX底层的MATLAB接口。我最推荐YALMIP。理由很实际YALMIP允许你用接近数学公式的符号化方式定义变量、约束和目标函数代码可读性高改模型也快。而且solver是解耦的今天用CPLEX明天想换Gurobi只需要改一个参数。对于随机调度这种多场景、多约束的模型声明式建模能省下大量索引管理的时间。optimproblem的问题在于处理T×S矩阵变量时不够直观尤其遇到需要逐场景循环定义约束时代码会变得很绕。直接写CPLEX接口Cplex类配合addCols、addRows性能最强但要手动装配系数矩阵适合模型结构固定、追求极致求解速度的工业项目不适合做研究和快速迭代。我的建议很直接先用YALMIP把模型跑通等确认模型没问题再考虑要不要针对性能做改造。绝大多数场景下YALMIPCPLEX就是最终方案。4.2 CPLEX配到MATLAB版本、路径和验证命令CPLEX配置到MATLAB的坑我踩过不止一次主要是版本匹配问题。CPLEX的MATLAB接口位于安装目录下的cplex/matlab/x64_win64第一步就是把该目录加进MATLAB路径addpath(C:\Program Files\IBM\ILOG\CPLEX_Studio201\x64\matlab\x64_win64); savepath;然后验证CPLEX能否被MATLAB识别cplex.getVersion();如果正常会返回版本号字符串。之后在YALMIP中测试求解器识别yalmiptest;看到CPLEX那行显示available就说明配置成功。这里有几个易错点一是32位MATLAB配64位CPLEX会直接失败必须架构一致二是CPLEX版本和MATLAB版本跨度太大可能出现兼容问题比如特别老的CPLEX在R2023b之后会报mex文件不兼容遇到这种问题优先考虑升级CPLEX而不是降级MATLAB三是许可证环境变量如果是网络许可证要确认LM_LICENSE_FILE或ILOG_LICENSE_FILE指向正确的许可服务器。注意CPLEX需要合法的商业或学术许可证。学术许可通常可以通过学校或官方项目申请花点时间走正规渠道比费心折腾环境变量和破解可靠得多。4.3 把数学公式翻成线性表达式的检查清单写完公式到写代码之间我一般会过一遍线性化检查清单保证CPLEX拿到的是一个标准的MILP目标函数里的每一项必须是变量×常数的组合不能出现两个变量相乘。充放电互斥这种自然要用Big-M加二进制变量。所有绝对值尽量拆成正负两个变量。比如购售电我不写Pgrid c_buy*max(Pgrid,0) - c_sell*min(Pgrid,0)而是直接定义Pbuy和Psell两个非负变量功率平衡用Pbuy - Psell这个线性化既简单又不会让求解器吃苦。储能SOC递推必须写成等式约束不要用不等式逼近。曾经我为了省变量用E E_last ...结果SOC越滚越偏调度结果物理上不可行。Big-M的取值是M值的一个平衡点太小会切掉可行解太大会让线性松弛变得很弱、求解变慢。我的经验是取对应变量物理上限的1.2~1.5倍。每写完一组约束先用小规模数据跑一遍检查目标值和约束违例量再放大规模。这习惯救过我很多次。5. 核心代码实现从场景生成到调度求解5.1 场景生成与削减一次跑通的小模块这一节把第三章的代码组装成完整函数。参数假设24时段初始抽样500个场景削减到30个代表场景。%% 场景生成与削减 T 24; S 30; N 500; rng(42); % 伪数据预测曲线 t (0:T-1); pv_pred LV * max(0, sin(pi * (t - 6) / 12)); % 光伏预测6点到18点 load_pred 300 100 * abs(sin(pi * (t - 8) / 16)); % 负荷预测带早晚高峰形态 % 抽样 u_load lhsdesign(N, T); load_err norminv(u_load, 0, 0.05); load_scen repmat(load_pred, N, 1) .* (1 load_err); u_pv lhsdesign(N, T); beta_val betainv(u_pv, 2, 5); pv_scen repmat(pv_pred, N, 1) .* (0.1 0.9 .* beta_val); % 联合削减 X [pv_scen, load_scen]; idx kmeans(X, S, Distance, sqeuclidean, Replicates, 10); prob histcounts(idx, S) / N; pv_rep X(idx mode(idx), 1:T); % 这里简化示意实际应取聚类中心C load_rep X(idx mode(idx), T1:2*T);严格来说取聚类中心应该用kmeans的第二个返回值C我在上面注释里写了实际代码里直接[idx, C] kmeans(...)C就是代表场景。上面这段示意主要是展示流程完整版可以在自己的函数文件里把C正确提取出来。5.2 YALMIP建模与CPLEX求解核心代码段有了代表场景和概率就可以构建优化模型。下面是核心代码变量定义、目标函数、约束循环都在里面%% 参数 Pgt_max 200; Pgt_min 20; R_up 60; R_down 60; c_fuel 0.5; % 燃气轮机单位燃料成本 c_ess 0.01; % 储能损耗惩罚 c_buy 0.4 * ones(T,1); c_sell 0.2 * ones(T,1); Pbuy_max 300; Psell_max 200; E_max 200; E_min 20; E_init 100; eta_ch 0.95; eta_dis 0.95; M_ch 80; M_dis 80; %% 决策变量 Pgt sdpvar(T, S); % 燃气轮机出力第二阶段变量 Pbuy sdpvar(T, S); % 购电 Psell sdpvar(T, S); % 售电 Pch sdpvar(T, S); % 储充 Pdis sdpvar(T, S); % 储放 Eva sdpvar(T1, S); % 储能SOC u_ch binvar(T, S); % 充放互斥 u_gt binvar(T, 1); % 燃气轮机启停第一阶段变量 %% 目标函数最小化期望成本 Cost 0; for s 1:S Cost Cost prob(s) * (sum(c_fuel * Pgt(:,s)) ... sum(repmat(c_buy,1,1) .* Pbuy(:,s)) ... - sum(repmat(c_sell,1,1) .* Psell(:,s)) ... sum(c_ess * (Pch(:,s) Pdis(:,s)))); end %% 约束 Con []; for s 1:S % 功率平衡 Con [Con, Pbuy(:,s) - Psell(:,s) pv_rep(s,:) Pgt(:,s) ... Pdis(:,s) - Pch(:,s) load_rep(s,:)]; % 燃气轮机约束启停变量不随场景变化 Con [Con, Pgt_min * u_gt Pgt(:,s) Pgt_max * u_gt]; Con [Con, -R_down Pgt(2:end,s) - Pgt(1:end-1,s) R_up]; % 储能约束 Con [Con, Eva(2:end,s) Eva(1:end-1,s) eta_ch * Pch(:,s) - Pdis(:,s)/eta_dis]; Con [Con, E_min Eva(:,s) E_max]; Con [Con, Eva(1,s) E_init, Eva(end,s) E_init]; Con [Con, Pch(:,s) M_ch * u_ch(:,s), Pdis(:,s) M_dis * (1-u_ch(:,s))]; % 电网约束 Con [Con, 0 Pbuy(:,s) Pbuy_max, 0 Psell(:,s) Psell_max]; end %% 求解 Options sdpsettings(solver,cplex,verbose,1); optimize(Con, Cost, Options);这段模型的规模大约是连续变量T×S×5 SOC变量(T1)×S ≈ 4320个二进制变量T×S T 744个约束约6000行。CPLEX求解通常秒级到一两分钟取决于场景数和机器性能。u_gt定义成T×1意味着燃气轮机开停计划在随机调度里是所有场景共享的这正是非预期性约束的体现。5.3 结果提取与物理合理性校验求解完成后用value()提取变量Pgt_opt value(Pgt); Pbuy_opt value(Pbuy); Psell_opt value(Psell); Pch_opt value(Pch); Pdis_opt value(Pdis); Eva_opt value(Eva); cost_expected value(Cost);提取结果后我必做三个校验第一是功率平衡残差。把解回代到功率平衡等式算出每时段、每场景的残差正常应当接近1e-6。如果残差大优先检查单位是否统一kW和MW混用是最常见的事故源。第二是SOC初末状态。Eva(end,s) E_init是我故意设置的周期约束让储能一天结束时不低于初始值保证调度策略具备可重复性。如果最优解里SOC总是贴着下限走说明储能的套利空间有限要么电价差太小要么c_ess惩罚设得过高。第三是备用稀缺场景识别。把所有场景里储能SOC触及下限、电网购电达到上限的时刻标出来这些就是系统的“脆弱时段”。下一步可以考虑在这些时段增加备用约束或者调整储能容量配置。这个分析比单纯看期望成本更有工程价值。6. 实测中的意外与调参经验6.1 场景数K的甜蜜点20、50还是100场景数太少代表场景不能覆盖分布的尾部结果会偏乐观场景数太多模型规模膨胀求解时间成倍增加。我的实测经验是对24时段、元件数量在10个以内的虚拟电厂S30到50个场景是甜蜜点。S从30增加到100期望成本的改善通常小于1%但求解时间可能从10秒涨到3分钟以上。怎么验证做法很简单分别在S20、30、50、100下求解画出期望成本和求解时间随S的变化曲线。期望成本趋于平坦、求解时间还在可接受范围内这个点就是合适的场景数。不要过度追求场景数多把计算资源省下来做敏感性分析更有价值。6.2 CPLEX求解慢、解不动的排查清单遇到求解变慢我按下面这张表逐一排查大多数问题都能定位现象可能原因处理办法下界长时间不涨gap一直卡在10%以上Big-M取值过大松弛太弱把M改成变量物理上限的1.2~1.5倍单纯求解时间越来越长但gap在缓慢下降整数变量过多检查是否有冗余的0/1变量例如可以用连续变量线性化替代的互斥约束求解器报数值警告或“Numerical difficulties”约束量纲悬殊统一单位尽量让所有系数在0.01~1000区间内MIP gap收敛到需要的高精度耗时太久精度目标过高设sdpsettings(cplex.mip.tolerances.mipgap, 0.01)工程应用1%~2%gap足够内存不足或求解卡死场景数太大先削减场景或启用cplex.optim.maxthreads限制并行线程减少内存峰值这里想单独强调一下Big-M。很多新手会把M取成1e6这种“足够大的数”这在MILP里是大忌。Big-M的本质是让约束在某种状态下“失效”但M太大相当于给线性松弛打开了宽阔的缝隙分支定界要探索的空间会几何级数膨胀。合理做法是让M紧贴变量的物理上限比如充放电功率上限80kWM就取80或96不要取1000。6.3 三个最容易翻车的细节第一个翻车点是场景概率忘记归一化。用K-means削减后histcounts返回的是落在每个簇的样本数必须除以总样本数才是概率。如果拿样本数直接用目标函数里场景权重加总不等于1期望成本会被系统性放大或缩小调度结果也会偏向样本多的场景。第二个翻车点是把光伏和负荷分开削减。我曾经为了省事分别对光伏和负荷做聚类再交叉组合结果场景数从30膨胀到900不说光伏的高发时段和负荷的高峰时段被错误组合生成了大量实际上不可能同时发生的场景调度计划的保守度失真的原因就在这。正确做法始终是联合削减把光伏和负荷拼接成一个向量再聚类。第三个翻车点是削减后的代表场景直接用聚类中心却不检查中心曲线的物理意义。K-means中心是均值可能出现光伏曲线在夜间非零这类不合理结果。我一般会加一个后处理把每个代表场景的负值截断为0光伏出力超过装机容量的部分拉回额定值再打印几组代表场景曲线肉眼扫一眼。这一步30秒就能完成但能避免后面讨论结果时被一句“这曲线不合理”问倒。最后分享一个小习惯每构建完一个随机调度模型我都会顺手跑一个“确定性对照”——把光伏和负荷场景都换成各自的中位数预测值用同一套约束求一版确定性解然后把这版解放到全部场景里回代看看它在哪些场景下会违约。这个对照实验能很直观地告诉你随机调度到底“多花了多少钱”、为了可靠性多买了多少冗余。拿这个结果去跟项目方汇报比任何理论解释都管用。建模的细节可以慢慢磨但这条验证思路值得一直留在自己的工具箱里。
返回列表