
做综合能源优化方向的朋友十有八九都经历过这种时刻单跑一个调度模型结果很漂亮一旦把容量配置和运行调度同时放进来还要把风电、光伏和负荷的不确定性考虑进去模型就开始变得复杂调试难度直接上了一个台阶。这篇内容来自“计及源荷不确定性的综合能源生产单元运行调度与容量配置优化研究Matlab代码实现”这个方向我在这里把它拆成可以直接参考的实现经验既要解释清楚为什么不能把容量配置和日运行调度分开做也要讲明白不确定性的场景怎么生成、模型怎么求解、代码怎么落地。如果你正准备做综合能源系统方向的研究、写毕业论文或者帮课题组搭一套可复现的容量配置算例这篇文章应该能帮你少走不少弯路。1. 问题梳理为什么源荷不确定性会直接决定容量配置结果1.1 不确定性到底体现在哪里又该怎么量化先说“源荷不确定性”这个词。综合能源生产单元里的“源”最常见的是风电、光伏这类受天气影响很大的可再生能源“荷”则是电负荷、热负荷这些用户侧需求。风电出力可能今天中午一阵风就冲上去明天同一时刻却几乎没有出力光伏则直接受云层、昼夜和季节影响电负荷和热负荷虽然有一定规律但节假日、极端天气都会带来明显波动。如果把这些量当成固定数值处理容量配置结果会怎样我举个很直观的例子假设某园区规划一台燃气轮机和一组储能若只用某一年的典型日数据做计算很可能得出“储能容量不需要太大”的结论因为那个典型日的夜间风速刚好很高电负荷波峰被风电填平了。但换一个风速偏低的年份同样的配置就会出现大量弃风和购电运行成本比预期高出一截。这就是“不确定性”必须被计入规划层的原因。数学上处理不确定性的主流思路有三类随机规划场景法给每个随机量生成多组可能取值并赋予概率最后求期望成本最小。鲁棒优化不关注概率只保证最坏情况下的方案仍然可行且成本可控。区间优化给每个随机量一个上下界区间在区间内做优化。对于容量配置问题我建议优先考虑随机规划中的场景法。原因很实际容量配置问题天然需要处理全年多时段的运行模拟把每种不确定情况变成“场景”后可以和时序运行约束直接结合物理含义清晰也方便在 Matlab 里用成熟的求解器计算。鲁棒优化虽然不需要概率分布数据但最坏情况往往在现实中几乎不会出现容量配置结果会偏保守设备投资高到不容易落地。1.2 容量配置与运行调度为什么不能分开优化很多人刚开始会问能不能先根据年平均负荷选设备容量再单独做日调度如果只做粗选可以但如果课题目标是“运行调度与容量配置一体化优化”就不能简单分开。因为容量配置决定了设备的投资边界而运行调度决定了同样容量下系统能省多少钱。一个 5 MW 的储能和 10 MW 的储能日调度策略完全不同。储能容量太小可能每天早晚高峰充电放电一次就把循环寿命耗尽容量太大大部分时间利用率很低。这种耦合关系只有放在同一个优化框架下才能体现上层回答“装多少”下层回答“怎么用”下层反馈的运行经济性再反过来决定上层容量该不该增加。所以这类问题在结构上天然适合“两阶段随机规划”或“双层优化”框架第一阶段确定容量配置第二阶段在给定容量下进行多场景运行调度目标函数综合考虑投资成本和运行成本的期望。2. 随机场景生成与削减把“不确定性”翻译成计算机能算的素材2.1 蒙特卡洛与LHS采样场景质量的关键第一棒要建立场景第一步是获得随机变量的样本集合。最常见的做法是先构建每个随机变量的概率分布模型比如风速用 Weibull 分布、光照用 Beta 分布、负荷误差用正态分布然后用蒙特卡洛抽样生成大量样本。但如果直接用蒙特卡洛抽样同样的分布函数样本数量小的时候很容易出现“局部扎堆”现象。比如我在模拟某地春季光伏时纯蒙特卡洛抽出的 200 个样本里可能因为随机数种子问题很多样本都集中在出力偏低区域导致后续场景削减得到的平均出力曲线偏离实际季节规律容量配置结果自然也偏了。用拉丁超立方采样LHS可以显著改善这个问题。LHS 的思想是把概率分布均匀分为 N 个等概率区间在每个区间内只抽取一个样本。这样只要 N 个样本就能保证整个分布域内都有覆盖均匀性远好于纯蒙特卡洛。实际操作中我一般先用历史数据拟合分布参数再对风电、光伏、负荷分别生成 10005000 组基础样本。样本生成之后统一存成矩阵方便后面削减去重。2.2 K-means聚类与同步回代削减用哪个场景数量过多会让优化模型规模爆炸。假设 5000 个样本每个场景都要求解 24 小时调度容量配置模型里的变量和约束数量会膨胀到数千甚至上万商用求解器也难以在合理时间内收敛。这时候必须做“场景削减”。常用的方法有两种同步回代削减法计算所有场景两两之间的距离把距离最近的两个场景合并成一个并累加概率不断重复直到场景数到达目标。K-means聚类直接把每个 24 小时时序样本看成一个多维向量用聚类算法把所有样本划分成 K 个簇每个簇的中心就是典型场景该簇样本占总数比例为场景概率。我之前更习惯用 K-means原因很直接实现简单Matlab 自带kmeans函数一次调用就能得到聚类中心和概率而且聚类结果的时序波动特征比单纯回代削减更平稳。同步回代削减也不是不好但在高维时序数据上容易出现两个“极端场景”被合并的情况比如一个高风电和一个低风电场景合并后平均场景变得不伦不类。场景削减数量怎么选从我的经验看24 时段问题一般保留 1020 个场景就足够。太少随机特征丢失太多太多计算量上不去但精度提升有限。我常做的检查是分别用 10、20、50 个场景计算同一组数据观察容量配置结果的差异。如果 10 个和 20 个场景之间结果差超过 5%说明场景代表性不足需要增多场景数如果 20 个和 50 个之间差异已经很小说明 20 个场景已经够用。以下是建议的场景数参数参考削减后场景数求解时间相对结果稳定性适用场景5很快较差可能遗漏极端情况只做教学演示1015较快较好能体现季节差异可行性研究、初算2030中等稳定适合正式报告论文算例、完整规划50以上慢与30差别不大需要高保真度的小规模系统2.3 不确定性情景的代表性检验场景削减完很多人直接就拿去做优化这一步容易埋坑。我的习惯是两个校验第一削减后所有场景的概率之和必须等于 1第二各场景的加权平均曲线要与原始样本的平均曲线尽量接近。加权平均曲线怎么检验直接把削减后 K 个场景乘以各自的概率再求和得到一条“期望场景”曲线画图和原始全部样本的平均曲线叠加。如果两条曲线偏差很小说明削减没有破坏整体统计特征。如果发现低谷时段偏差明显大于 5%就要提高聚类 K 值或者改用其他距离度量方式。这个环节我吃过亏。有次我用一个 6 场景的削减结果跑容量配置燃气轮机容量偏小原因就是削减后的风电场景在傍晚出力平均值被明显拉低系统需要燃气轮机支撑更多负荷。后来把场景数加到 15结果才恢复正常。所以场景削减并不只是“降维”它直接影响容量决策的正确性。3. 双层优化建模与三种求解路线3.1 上层投资容量决策层模型在整个模型中上层决策变量是各设备的安装容量包括光伏、风电、燃气轮机、电锅炉、储能功率、储能容量等。目标函数不只有初始投资成本还需要把设备寿命、年维护成本折算进去否则很容易出现“前期安装成本低、后期运行费用失控”的结果。年化投资成本的计算相对统一单位容量投资成本 × 设备容量 × 资金回收系数CRF。资金回收系数的意义是把一次性投资平均分摊到寿命期内每一年的成本里使投资成本和运行成本可以用同一个时间尺度比较。没有这一步投资成本和单日运行成本直接相加就是错误的因为投资成本是一次性支出而运行成本是每日产生的。统一优化目标的简化形式大致是min 年化投资成本 Σ(场景概率 × 该场景年运行成本)同时约束包括各设备安装容量不超过资源上限或场地上限储能容量与储能功率之间可以有耦合约束不同能源类型的容量之间也可以体现技术互补关系。在实际代码中容量变量一般设置成连续变量因为电力设备的容量选择通常按连续的 MW 值建模。如果研究目标是设备选型而不是容量优化比如要在几个固定规格的变压器或储能模组里做选择就需要引入整数变量这时候模型就变成混合整数规划。3.2 下层多场景运行调度层模型上层给定一组容量方案后下层需要对该方案在所有场景下进行 24 小时运行调度算出一个期望运行成本。调度决策通常包括各个可调设备逐时出力、储能逐时充放电功率与 SOC 变化、与大电网交互的购售电功率、必要时还有可削减负荷量。调度的目标函数一般是让购电成本、燃气成本、设备启停成本等尽可能低。约束条件包含电功率平衡光伏、风电、燃气轮机、储能放电、购电之和等于电负荷、储能充电、电锅炉耗电、售电之和。热功率平衡燃气轮机余热、电锅炉产热等要满足热负荷需求。设备出力上下限和爬坡约束。储能 SOC 递推约束以及调度周期始末 SOC 相等的约束。与大电网交互的功率上下限。这个子问题本身是一个线性或混合整数线性规划规模可控。当场景数量不大时整合进上层模型后可以用商用求解器直接求解。3.3 求解思路从嵌套到KKT再到整体转化容量配置与运行调度联合优化的标准解法有三种思路不同场景下选择不同不能一概而论。第一种思路是“内外层嵌套循环”外层用智能算法或枚举方法生成一组容量方案内层用商业求解器做当前容量下的运行调度把调度成本返回给外层评估适应度。这种方式实现简单在 Matlab 里写个循环就行缺点是内层每评估一次都要调一次求解器如果外层种群规模 50、迭代 100 代内层就要跑 5000 次调度速度非常慢且智能算法无法保证找到全局最优。第二种思路是“下层 KKT 条件转换”把内层调度的最优性条件写出来再将下层问题替换成这些约束从而变成一个单层数学规划问题。好处是把嵌套变成了单层坏处是下层是凸二次或线性规划时才能这样做而且转换后通常要处理互补约束需要大 M 法引入二进制变量数值稳定性容易出问题。第三种思路最实用也是我更推荐的直接按“两阶段随机规划”合并成一个大规模 MILP。思路是把容量配置作为第一阶段变量各场景下的调度变量作为第二阶段变量目标函数写成投资成本加上所有场景运行成本的期望。由于调度变量在不同场景之间互不耦合只有容量变量是公共的这个大规模模型可以直接交给 Gurobi 或 CPLEX 求解。第三种思路要求整个模型能写成线性问题因此关键在于约束的线性化处理比如储能充放电不能同时进行的约束、设备分段效率等都需要通过引入二进制变量和大 M 系数来处理。4. Matlab工程化实现代码架构与关键模块4.1 数据结构怎么组织最顺手Matlab 代码能不能稳定跑通很大程度上取决于数据组织。我一般会把所有参数集中放在一个结构体数组里而不是散落在一堆 script 中。这样后期调整风电场容量、负荷峰值倍数、电价参数时只需要改 initParams.m 一个文件。基础数据结构可以这样设计param.device.pv struct(cost_per_kw, 3500, life, 25, max_area, 20000); param.device.wt struct(cost_per_kw, 6000, life, 20, max_num, 5); param.device.gt struct(cost_per_kw, 4500, life, 20, efficiency, 0.4); param.device.eb struct(cost_per_kw, 1200, life, 15, efficiency, 0.95); param.device.ess struct(cost_per_kwh, 1500, cost_per_kw, 800, life, 10); param.price.buy [0.8 * ones(1,8), 1.2 * ones(1,8), 0.6 * ones(1,8)];场景数据我习惯用三维矩阵存场景数 × 时段数 × 变量类型。比如scenario.electric_load是一个K×24的矩阵K 是场景数。这样后续写约束时可以直接按整个矩阵操作避免反复用 for 循环大幅提高代码效率。4.2 LHS和场景削减的Matlab实现示例LHS 抽样在 Matlab 中可以直接用lhsdesign或lhsnorm实现。如果已经通过历史数据得到了预测误差的均值和协方差用mvnrnd或者lhsnorm都能生成多维样本。一个简单的示例num_samples 2000; T 24; mu zeros(1, T); % 预测误差均值假设为0 Sigma 0.1 * eye(T); % 简化协方差矩阵 err_samples mvnrnd(mu, Sigma, num_samples); % 将误差叠加到确定性风电出力预测曲线上 wind_base [ ... ]; % 1x24 风电预测出力 wind_samples wind_base err_samples; wind_samples max(wind_samples, 0);场景削减用 K-means 时直接对多变量样本做聚类。这里有一个小技巧如果直接对 24 维风电场景聚类可能出现夜间时段权重过高而白天差异被忽略的情况最好在聚类前先归一化每个变量的方差或者按物理规律给不同时段设置权重。K 15; idx kmeans(wind_samples, K, Distance, sqeuclidean, Replicates, 20); centers zeros(K, T); probs zeros(K, 1); for k 1:K members wind_samples(idx k, :); centers(k, :) mean(members, 1); probs(k) size(members, 1) / num_samples; end聚类时Replicates设为 20是因为 K-means 对初始中心敏感多次复现可以避免陷入局部最优。如果数据量大可以只设 10 次节省时间。4.3 YALMIP建模与Gurobi/CPLEX调用Matlab 下做优化最省事的工具箱是 YALMIP 加 Gurobi 的组合。YALMIP 负责写约束和目标函数Gurobi 负责求解。容量配置模型用 YALMIP 写非常直观不需要像直接用intlinprog那样把所有约束写成矩阵形式。以核心目标函数为例% 决策变量容量配置第一阶段 C_pv sdpvar(1, 1); C_wt sdpvar(1, 1); C_ess sdpvar(1, 1); P_ess sdpvar(1, 1); % 各场景调度变量第二阶段 P_gt sdpvar(K, 24, full); P_eb sdpvar(K, 24, full); P_ch sdpvar(K, 24, full); P_dis sdpvar(K, 24, full); P_buy sdpvar(K, 24, full); Constraints []; % 示例约束电功率平衡 for k 1:K Constraints [Constraints, ... P_pv(k,:) P_wt(k,:) P_gt(k,:) P_dis(k,:) P_buy(k,:) ... load_e(k,:) P_ch(k,:) P_eb(k,:)]; end objective annual_invest_cost sum(probs .* annual_oper_cost); ops sdpsettings(solver, gurobi, verbose, 2, gurobi.MIPGap, 0.01); optimize(Constraints, objective, ops);写这个模型时有几个关键点需要注意。第一储能充放电状态变量会引入二进制变量会让求解时间显著上升所以在线性化时尽量不要给每条 SOC 递推约束都加一组二进制变量可以考虑先做预分析判断某些时段不可能同时充放电进而减少整数变量。第二由于 24 时段的问题规模本身不算特别大Gurobi 默认参数通常几分钟内就能出结果不需要过度调参。如果场景数和时段数增加就要打开 Gurobi 的 MIP Gap 设置让求解器在可接受的误差范围内提前收敛。4.4 结果合理性检查与绘图容量优化计算结束后最容易犯的错误是拿着结果直接写结论。先别急先做三类检查第一检查储能 SOC 曲线是否有周期性。SOC 曲线应该在 0.10.9 之间平滑波动如果某天从 0.2 直接跳到 0.9 再跳回 0.2说明充电功率或能量约束写错了。第二检查同一时段是否出现明显的购电和售电同时发生。这是很多初级模型会犯的错误因为设计目标或者约束没有禁止倒买倒卖套利。第三检查燃气轮机是否出现频繁启停。燃气轮机连续运行比频繁启停更符合实际如果结果出现大量单时段启停要考虑给启动成本加约束或惩罚项。绘图时我用stairs画各设备出力堆叠图比普通折线图更容易看到供需平衡关系。再画一张储能 SOC 与电价的对比图可以直观判断储能是否在低电价时段充电、高电价时段放电。如果 SOC 曲线和电价走势完全无关说明目标函数里购电成本的权重可能设置有问题。5. 调试中常见的坑与解决实录5.1 内层调度无可行解这个问题我遇到频率最高。现象是Gurobi 报infeasible problem模型完全不可行。排查顺序一般是先看电平衡约束特别要注意符号方向。储能充电是“负荷”不是“电源”很多第一次建模的人会把等式两边的充放电方向写反。再看 SOC 的递推约束如果第 1 时段初始 SOC 与周期末 SOC 相等约束一起加但储能容量特别小可能导致末时段为了满足相等约束需要大幅度充电却受充电功率上限限制这种问题在风电出力偏低的场景里尤其明显。处理办法也很直接给储能周期末 SOC 加一个较小的松弛范围比如允许末时段 SOC 与初始 SOC 之差在 ±5% 以内并把这个差值作为软约束放进目标函数罚掉。这更符合实际运行习惯也让模型不至于因为边界条件过于苛刻而无解。5.2 大M参数引起的病态无论是处理储能充放电互斥、购售电互斥还是 KKT 条件转换都会用到“大 M 法”。大 M 取值太大会破坏数值稳定性太小又可能把不可行的状态判断为可行导致错误的二进制变量取舍。我习惯上先把每个约束里涉及的物理量最大可能值算出来再取该值的 25 倍作为 M 值。比如购电功率上限是 10 MW分时价格差异不大那 M 取 20 或 30 就够不需要写成 10000。如果模型里同时有好几十个大 M 系数可以考虑把 M 设置成不同的值尽量避免统一设置一个巨大的 M 值因为那样往往会让求解器在预求解阶段产生很多无意义的割平面。5.3 场景削减过度导致结果失真场景削减数量过少最常见的症状是容量配置结果在几次独立实验之间差异很大。比如两次运行代码只改了随机数种子光伏装机结果却从 15 MW 跳到 30 MW说明场景代表性不足。解决办法有两个方向。一是提高削减后的场景数量直到结果差异缩小到可接受范围。二是不要只做一次场景削减可以在相同 K 值下用不同的 K-means 初始值重复 5 次选择组内距离最小的那组结果。我在代码里通常直接对聚类中心做多起点搜索能明显提高稳定性。更有经验的思路是保留少量“极端场景”专门用来做约束校验而不是全部参与期望成本计算。比如把光伏出力最低、风电出力最低、负荷最高的场景专门加进去做可行性校验这样得到的容量配置不会在极端天气下完全失去平衡能力。这些场景概率设置为很小值主要是起到约束作用。5.4 算法不收敛且总时间过长如果模型规模大Gurobi 默认参数不一定能满意求解。我建议从这些方向入手第一优先减少整数变量。储能是否同时充放电的状态变量最常见可以尝试把储能建模成“半连续变量”即要么为 0要么在最小功率和最大功率之间连续变化减少一组二进制变量。第二把 24 时段调度降为关键时段抽样。比如研究典型日时不一定要全天 24 点都参与优化可以取峰、平、谷三个典型电价时段各时段内保持恒定功率虽然精度下降但搜索空间缩小极多。第三设置gurobi.MIPGap, 0.05而不是默认的 0.0001。容量配置问题本身存在模型误差5% 的 MIP Gap 完全足够工程决策。5.5 一个容易被忽略的初始数据门槛最后再分享一个我在实际项目中很容易踩到的坑这个方向的难点经常不在优化模型本身而在基础数据的筛选与清洗。风电、光伏、负荷数据的缺测、坏数据、时间戳不对齐都会直接影响场景生成结果。有次我拿到一个数据集凌晨 2 点的光伏出力仍然大于 0就是因为时区没有统一处理导致场景削减后白天时段光伏出力被系统性低估。后来我把数据清洗环节提到最前面先做时序对齐与合理性检查再进入场景生成流程。整个过程跑通后容量配置结果稳定了很多也不再需要反复调大 M 系数来掩盖数据层的错误。综合能源生产单元的容量配置本质上不是单纯求一个设备规模数字而是在多种随机源和负荷曲线下找到“投资可控、运行可调、风险可接受”的平衡点。Matlab 代码可以帮你把这一步自动化但前提是建模思路合理、数据可靠、参数设置经得起推敲。希望这份实操梳理能给你一个相对完整的起点。