做综合能源系统调度优化这几年,我最大的感受是:模型再漂亮,落地时最头疼的往往不是设备本体,而是新能源出力那股“说不准”的劲。光伏清晨爬坡、午后飘云,风机一阵风过来出力猛涨、一阵风过去近乎归零,如果优化模型还是拿一个固定的预测值去排计划,实际运行时的功率偏差就会让调度员非常被动。这也是我为什么把“计及新能源出力不确定性的电气设备综合能源系统协同优化”作为核心课题,并最终用Matlab把整套算法完整跑通的原因。
这篇文章不是教科书式的理论堆砌,而是一个可复现的项目经验梳理。我会从模型边界、不确定性建模、协同优化求解、Matlab代码实现、仿真结果分析和踩坑记录六个方面展开。不管你是刚接触综合能源优化的研究生,还是已经在做园区级能量管理系统的工程师,这篇文章应该都能帮你少走不少弯路。
1. 先把模型边界说清楚:这个“电气设备综合能源系统”到底优化什么
标题里的“电气设备综合能源系统”,我倾向于理解为广义的电气化能源设备集合,也就是由电力、天然气、热力等多种能源转换与存储设备构成的园区级供能系统。最典型的成员包括:光伏、风电、电储能、燃气轮机或热电联产机组、电锅炉、热储能,以及电负荷、热负荷、气负荷三类用能需求。调度中心要回答的问题是:未来24小时,每台设备在每个时刻该发多少电、烧多少气、充多少储、放多少热,才能让总运行成本最低,同时满足各类负荷的供需平衡和安全约束。
1.1 设备构成与决策变量的对应关系
先列一个我在建模时一定会先画的表。这张表的作用不是装饰,而是把“物理对象”和“数学变量”一一对应起来,防止后面写约束时张冠李戴。
| 设备 | 核心决策量 | 典型出力范围 | 主要约束来源 |
|---|---|---|---|
| 光伏/风电 | 实际并网功率 | 0到预测上限 | 出力不确定区间、弃电控制 |
| 电储能 | 充电/放电功率 | -额定功率到额定功率 | SOC上下限、充放功率限、周期回位 |
| CHP热电联产机组 | 发电功率、供热功率 | 电出力上下限、热出力上下限 | 热电比可行域、爬坡约束 |
| 电锅炉 | 产热功率 | 0到额定功率 | 电热转换效率、用电上限 |
| 热储能 | 充/放热功率 | -额定功率到额定功率 | 储热罐容量约束 |
| 外购电力 | 从上级电网购入功率 | 0到购电上限 | 购电合同上限、功率平衡 |
| 外购天然气 | 从气网购气流量 | 0到购气上限 | 气网供应上限 |
这张表列完之后,整个优化问题的骨架基本就出来了。决策变量是一个多维时变序列,优化维度通常是“设备数量 × 调度时段数”。如果做24小时日前调度、时间粒度取1小时,那么一个中等规模园区的决策变量就有几百个,再加上0-1启停变量,规模达到数千维并不稀奇。
1.2 为什么“协同优化”而不是各设备独立调度
这是我自己体会最深的一点。很多人一开始会把问题拆成“电系统一个优化、热系统一个优化”,然后单独排计划,最后发现根本合不上。原因在于综合能源系统里电和热是强耦合的:CHP机组发一度电的同时会带出一部分热,电锅炉用电烧热又把电负荷抬高,而热储能则可以把“热电联产”和“电转热”两条供热路径在时间上解耦。所以必须把电力平衡、热力平衡、燃气供应放在同一个优化框架里同时求解,才能找到真正的全局最优。
举个实际例子。在一个冬季典型日里,如果只盯着电力成本,调度方案会让你在电价低谷时段多买电、少开CHP,但如果把热负荷也纳入目标,你会发现在电价低谷时段用电锅炉烧热存进储热罐,白天高峰时段再由储热罐放热,反而比单纯调节CHP更省钱。这类跨能源品种的套利机会,只有协同优化才能捕捉到。
1.3 数学模型的总体框架
用一个概括性的表述来说,这个优化问题的标准形式是:
目标函数:最小化总运行成本,包括购电费用、购气费用、设备启停成本、运维成本,以及弃新能源惩罚和失负荷惩罚。
约束条件包括:电功率平衡约束、热功率平衡约束、气源购气约束、各设备出力上下限、储能SOC转移方程、机组爬坡约束、外购电量上限等。
我通常把这个模型写成混合整数线性规划(MILP)来处理。虽然原问题里有一些非线性环节,例如设备效率随负载率变化,但通过分段线性化之后,可以在不牺牲太多精度的前提下大幅降低求解难度。这一点我在第3章还会详细展开。
2. 新能源出力的不确定性建模:场景法、鲁棒法与模糊机会约束的选择逻辑
明确了优化问题之后,接下来最关键的一步就是回答:“不确定的新能源出力到底怎么放进优化模型?”这一步选型直接决定了求解难度和结果的保守程度,需要在建模阶段就考虑清楚。
2.1 不确定性从哪来、影响有多大
光伏和风电出力的不确定性主要来自预测误差。我们通常有一个预测曲线,比如“明天9点到15点光伏预测功率约80%额定容量”,但真实出力可能因为云层遮挡、风速变化、温度漂移而偏离预测。在确定性优化里,调度员把这个预测值当作硬约束,只要实际出力低于预测,系统就会面临功率缺额;如果实际出力高于预测,又可能出现弃电或潮流越限的问题。
这里要特别说一个容易被忽视的细节:不确定性不只是“出力大小”的变化,还涉及“时间相关性”。比如午后的光伏爬坡事件,如果预测模型假设出力是平滑的,而实际在10分钟内跌掉一半,那么只做单一时刻的静态优化是完全不够的。所以我在建模型时习惯于把所有不确定量的时序特征保留住,把场景作为一个完整的时间序列来生成,而不是一时刻一时刻孤立抽样。
2.2 三条技术路线的对比
目前主流的建模方法可以归纳为三类,我在实际项目中都试过,各自的逻辑和适用场景差别很大。
场景法(随机规划)的思路是:用概率场景集合刻画可能的光伏/风电出力曲线,每个场景有一个概率权重,目标函数取所有场景下的期望成本。这种做法信息量最丰富,结果也最贴近真实运行,但计算代价随场景数量上升非常快。
鲁棒优化法的思路是:不关心具体概率分布,只给定一个不确定集合(比如出力在预测值上下浮动的区间),优化目标是在最恶劣情况下的成本最小化。它的优点是对分布误差不敏感、求解相对高效,缺点是非常保守,有时为了应对极低概率场景,会把调度成本推高10%以上。
模糊机会约束法则是把约束满足度做成概率形式,例如“功率平衡被满足的概率不低于95%”,然后用清晰等价类把概率约束转换成确定性的形式。它比鲁棒法灵活,比场景法计算量小,但对分布参数的建模能力要求较高。
三种方法的对比我整理成了表格,方便你根据自己的数据条件做选择题。
| 方法 | 所需数据 | 计算量 | 保守程度 | 模型复杂度 | 工程落地难度 |
|---|---|---|---|---|---|
| 场景法 | 历史出力数据、预测误差分布 | 中高 | 中低 | 低 | 低 |
| 鲁棒法 | 出力上下界区间 | 低 | 高 | 中 | 低 |
| 模糊机会约束 | 预测误差分布参数 | 中低 | 中 | 较高 | 需要概率转化 |
2.3 场景生成与削减的实操做法
如果选了场景法,接下来就要解决“场景怎么来”以及“场景太多怎么办”这两个问题。我推荐的做法是误差分布采样加场景削减两步走。
第一步,根据历史数据统计每个时段新能源出力的预测误差,假设误差服从正态分布或拉普拉斯分布。用拉丁超立方采样代替普通蒙特卡洛采样,可以保证覆盖概率空间的均匀性,同样的样本数量下效果明显更好。第二步,对生成的大规模场景集做场景削减,常用手段包括同步回代削减法和聚类削减法。我自己试下来,先用K-means聚类把几百个场景聚成30到50个典型场景,再对每个聚类分配概率权重,平衡效果和计算速度都很理想。
2.4 我在代码里实际采用的方案
在这个项目里,我最终采用了“场景法为主、鲁棒边界为辅”的混合思路。核心框架是随机规划的场景法,同时给最重要的功率平衡约束加上一个鲁棒化的备用容量项。这么做的好处是,典型的调度结果兼顾了经济性和健壮性,不会因为某几个极端场景而把成本撑得过高。准确说,目标函数仍然是所有场景下的期望运行成本,但约束里多了一条“系统上调备用和下调备用分别不低于预测误差区间”,这等于给不确定性加上了一道安全底垫。
3. 协同优化的数学建模与求解器配置:目标函数、约束条件与线性化处理
模型框架定下来后,真正动手写Matlab代码前,必须先把目标函数和约束条件在纸面上列清。这一步如果偷懒,后面调试代码时一定会付出好几倍的代价。
3.1 目标函数:运行成本构成的拆解
我采用的日前调度目标函数为最小化总运行成本:
第一项是外购电力费用,电价采用分时电价曲线,有的园区还有容量费或需量费,我通常把容量费转化成对购电功率峰值的惩罚项一并放入目标;第二项是天然气购气费用;第三项是设备启停成本,主要来自于CHP这一类的大型机组,频繁启停不仅磨损设备,还会在热力学上造成额外燃料消耗;第四项是设备运维成本,可以简化成出力或功率的线性函数;第五项是弃新能源惩罚项,用于软化“新能源出力必须全额消纳”的硬约束,惩罚系数需要设置得比购电和购气成本高一些,才能让优化器在几乎所有情况下都优先消纳新能源。
这里有一个很容易犯的错误:惩罚系数设置不当。如果弃电惩罚设置得太低,优化器会在某些时段宁可通过弃电来避免调节其他设备,导致新能源消纳率异常。如果设置太高,又会迫使系统在极端场景下花高昂代价去消纳边际效率极低的新能源出力,抬升总成本。我通常的做法是把弃电惩罚设为最高电价水平的两到三倍,再根据结果做敏感性分析微调。
3.2 核心约束条件的写法
约束条件里,电功率平衡是最基础的一条:各发电设备出力加储能放电功率加外购电,减去储能充电功率减去用电负荷,要等于零。热功率平衡类似:CHP供热加电锅炉产热加热储能放热,减去热负荷和热储能充热功率,等于零。
设备出力上下限和爬坡约束也需要结合实际情况考虑。比如CHP机组,不能简单把它当作一个可发任意功率的电源,它存在一个热电耦合可行域:在纯凝工况下发电效率高、供热为零,在抽凝工况下发电供热同向变化。我在建模时用多边形可行域近似这个热电耦合关系,用几条线性不等式把可行域包络出来,效果比单一固定热电比要好得多。
储能约束更是重头戏。电储能和热储能的SOC状态转移方程是典型的动态约束:
SOC(t+1) = SOC(t) + 充电功率 × 充电效率 - 放电功率 / 放电效率
这块必须注意效率的放置位置,放错位置会直接导致“越充越亏”或“越放越赚”的荒谬结果。另外一个工程上常见的硬性要求是调度周期末SOC要回到初始值附近,否则电池无法做循环往复的日常调度,会持续漂移到满电或空电状态。
3.3 非线性环节的线性化处理
原模型中存在两类常见的非线性环节:一是设备效率随负载率变化,二是包含0-1整数变量的启停逻辑。第一类可以通过分段线性化逼近,把每台设备的电热转换关系用多个线性段表示;第二类则需要引入大M法或特殊顺序集来线性化。虽然这会增加约束数量,但换来的是可以使用商用线性求解器,求解速度快且解的质量有保证。
我在实际代码里面用Yalmip做建模层,求解器选用CPLEX或Gurobi。对于30个场景、96个时段的典型算例,模型规模通常在数万约束量级,求解时间在几十秒到几分钟之间,完全可以满足日前调度的计算需求。
3.4 求解器选型和运行环境配置
如果你已经有了Matlab环境,请务必安装Yalmip工具箱,并接好一个可用的混合整数线性规划求解器。环境配置上有一个容易踩的坑:Yalmip里指定求解器的方式是sdpsettings('solver', 'gurobi'),如果你安装的是CPLEX,要对应改成cplex。另外还要注意求解器许可证的位数必须与Matlab位数一致,64位Matlab配32位求解器接口时经常出现无法加载的动态库报错。
4. Matlab代码实现的运行流程与核心细节
模型数学形式明确之后,代码实现的难度已经降了大半。这一章我就直接分享我的代码架构,以及几个真正影响运行效果的关键细节。
4.1 代码模块怎么划分
我的项目代码文件组织如下:
- main.m:总入口,负责读取参数、调用各模块、汇总结果并绘图。
- load_data.m:读取负荷曲线、新能源预测曲线、设备参数、电价和气价。
- scenario_gen.m:生成新能源不确定性场景集,并完成场景削减。
- build_model.m:用Yalmip定义决策变量、目标函数和约束条件。
- solve_model.m:调用求解器并处理求解结果。
- plot_results.m:绘制电功率平衡、热功率平衡、储能SOC、新能源消纳率等曲线。
模块划分最核心的原则就是数据生成和模型求解分离。这样做的好处非常实际:当你想调整场景数量、修改预测误差参数时,只需要改scenario_gen.m,不需要动build_model.m里的任何约束;当你想增加一个新设备时,也只需要在build_model.m里追加变量和约束,不用重写数据预处理逻辑。
4.2 场景生成模块的代码骨架
场景生成模块我用拉丁超立方采样加K-means削减的写法。下面这段代码是核心逻辑:
% scenario_gen.m 核心片段 Nscenes = 1000; % 初始抽样场景数 K = 30; % 削减后的典型场景数 T = 96; % 调度时段数 mu = Ppv_forecast; % 光伏预测曲线 sigma = 0.1 * mu; % 标准差按预测值10%设置 % 拉丁超立方抽样 lhs_samples = lhsdesign(Nscenes, T); scenes = zeros(Nscenes, T); for t = 1:T scenes(:, t) = norminv(lhs_samples(:, t), mu(t), max(sigma(t), 0.01)); end scenes = max(scenes, 0); % 出力非负 % 限制在装机容量内 scenes = min(scenes, Ppv_cap); % K-means场景削减 [idx, C] = kmeans(scenes, K, 'MaxIter', 500, 'Replicates', 5); prob = histcounts(idx, K) / Nscenes;这段代码里有个细节值得注意:标准差按预测值的比例设置,因为光伏出力的预测误差通常随着出力水平变化,而不是恒定绝对量。夜间光伏出力趋近于零时,误差也相应变小,所以我会加一个0.01的保底下限,避免极端抽样值频繁出现。
4.3 模型构建模块的Yalmip写法
build_model.m中用Yalmip定义变量的方式是整个代码的核心。我习惯用三维变量,第一维是设备编号,第二维是时段,第三维是场景索引。如果一个变量不随场景变化,那就定义成两维。这个区分非常重要:日前调度中,启停计划和购电决策可以是“非预期”的,也就是在不确定性实现前就要确定下来,而机组实际出力、储能充放电可以随场景自适应调整。用专业术语说,这是两阶段决策结构,第一阶段变量与场景无关,第二阶段变量依赖场景。
% build_model.m 核心片段 % % 变量定义 % g(i, t) 表示设备i在t时段的出力,s表示场景 g = sdpvar(repmat(Ndev, 1, 1), T, K); % 依赖场景 u = binvar(Ndev, T); % 0-1启停变量,不依赖场景 % 目标函数:场景期望成本 Objective = 0; for k = 1:K Objective = Objective + prob(k) * ... (购气成本(k) + 购电成本(k) + 启停成本 + 运维成本 + 弃电惩罚(k)); end % 约束条件拼接 Constraints = []; for k = 1:K Constraints = [Constraints, 电功率平衡表达式 == 0]; Constraints = [Constraints, 热功率平衡表达式 == 0]; % 设备上下限、储能SOC、爬坡、备用等约束 end实际代码中不会把所有成本项真的写成“购气成本”这种中文变量,这里只是表达结构。我需要强调的一点是:第一阶段变量和第二阶段变量的区分,直接决定了模型是否符合“非预期性”原则。如果不做这个区分,把所有变量都设置成随场景变化的,求解器就会获得一种信息上的“先见之明”,得到过度乐观的结果。这是随机优化里最容易错的模型设定,没有之一。
4.4 求解结果的处理与可行性检查
求解完成之后,不要急着画图,先做一步基础检查:查看求解器的返回状态,确认问题被求解到最优或至少在可接受的gap范围内。我习惯用以下代码:
sol = optimize(Constraints, Objective, options); if sol.problem ~= 0 warning('求解状态异常: %s', yalmiperror(sol.problem)); else g_opt = value(g); u_opt = value(u); end如果返回值不是0,最常见的两个问题是“不可行”和“数值问题”。前者说明约束之间互相矛盾,比如某时段热负荷太高而所有供热设备上限加起来都不够;后者通常是量纲不一致导致的,比如功率用kW,能量用MWh,储能SOC方程里差了个倍数。我会在下一章详细讲这类坑的排查方法。
5. 仿真结果分析:不确定性水平对调度策略与经济性的影响
代码跑通之后,最需要回答的问题是:这个模型相对于确定性模型,到底带来了什么价值?我通过一组对照实验来说明结果差异,实验中只改变新能源预测误差的标准差,从5%逐步提高到15%,观察成本和调度策略的变化。
5.1 实验设置与经济性对比
基准场景是某园区冬季典型日,包含光伏100MW、风电50MW、电储能20MW/40MWh、两台50MW级CHP机组、电锅炉30MW和热储能50MWh。负荷曲线来自该园区实测数据的典型剖面,电价采用峰平谷三段式结构。
我在不同误差水平下分别跑确定性模型和场景随机优化模型,得到的总运行成本如下:
| 误差水平 | 确定性模型成本(万元) | 场景法模型成本(万元) | 成本增幅 |
|---|---|---|---|
| 5% | 128.6 | 129.8 | 0.9% |
| 10% | 128.2 | 132.4 | 3.3% |
| 15% | 127.9 | 137.6 | 7.6% |
这个表格里有一个反直觉的结果:确定性模型的成本随着误差增大反而下降。这不是因为它变得更好,而是因为确定性模型使用的是预测出力均值,误差越大,它就越敢减少备用、少开机组,把成本“虚低”了。一旦实际场景偏离预测均值,这个低成本的计划根本没有可执行性,实际运行会发生大量切负荷或弃风弃光。换句话说,确定性模型的成本是“数据偏差掩盖出来的好消息”,而场景法的成本才是真正能落地的期望值。
5.2 调度策略的差异在哪
进一步看设备出力曲线,有两处规律非常明显。
第一,不确定性越大,CHP机组的出力水平越高,运行方式更稳定。原因是CHP被视为园区内部的可靠电源,不仅发电,还能供热,等于同时为电系统和热系统提供支撑。优化结果倾向于利用这种“电热双保险”来对冲新能源出力波动。
第二,电储能在不确定性增大时不再只盯着峰谷价差套利,而是更多地保留“备用余量”。典型表现是:确定性模型里储能往往在谷电时段充满、峰电时段放空,表现出很强的套利特征;而场景模型给储能设置了一个最低SOC门槛,例如不低于20%或更高,以保证在出现低光伏场景时仍有足够的上调备用来平衡系统。储能从“纯套利工具”变成了“套利加备用”的双重角色,这是不确定性计入模型后最本质的变化。
5.3 从结果中得到的运行启示
这对实际运行有三点启示。一是调度计划必须预留足够的备用容量,不能把新能源预测值当作实际值来排计划。二是热储能的价值在不确定性环境下被放大,因为它既能参与热平衡调节,又能间接为CHP“解耦”提供缓冲,让机组在电供给富余时段仍然保持较高的可用性。三是备用容量的分布不能只盯着电网侧,分布式发电和储能同样应该承担一部分备用责任,否则所有风险都压在电网主变上,容易造成购电费用和功率越限同时飙升。
6. 写码和调参过程中的踩坑记录
最后分享几个我在这个项目里真实踩过的坑。这些坑的共性是:模型在纸面上完全正确,但进了代码就出问题,而且报错信息往往不会直接指向病因。
6.1 场景数贪多导致求解时间爆炸
最开始我把场景数设成了200个,想着场景越密结果越精确,结果模型求解时间直接飙到几十分钟以上,而且求解器内存占用接近失控。后面做了场景削减实验,发现把200个场景削减到30个,成本计算结果只偏差了大约1%,但求解时间从几十分钟降到了两分钟以内。这个性价比非常惊人。我的建议是:场景数用“阶梯式增加”的方式试探,从20个开始,逐步增加到50个,观察目标函数值的变化,当增加场景数量带来的成本变化小于0.5%时,就没有必要继续增加了。
6.2 储能SOC约束写错引发的“永动机”
这是最典型的一个模型错误。我在储能约束里曾把充电效率和放电效率的作用位置搞反,导致约束在数学上允许系统在同一时段同时充电和放电,通过来回充放凭空产生能量。这类错误在结果图上非常隐蔽,因为整体功率平衡仍然成立,但仔细观察单个设备的功率曲线会发现储能充放电经常同时大于零。排查方法是写一个后处理校验脚本,对所有时段检查储能SOC变化是否严格等于“充电量乘效率减放电量除效率”,一旦发现偏离就立即定位到约束公式。
6.3 量纲不一致的灾难
有一版代码里,负荷数据来自现场采集系统,单位是kW,电价数据单位是元/MWh,燃气热值单位是kJ/m^3,我在目标函数里直接相加,导致求解结果完全不可解释。后来我强制要求所有输入参数进入模型之前统一转换为标幺值或统一量纲——我习惯用MW、MWh、元/MWh作为基准单位。在load_data.m里做一步单位换算并打印日志,每类参数换算完都复核一遍峰值,基本能拦住这类问题。
6.4 “可弃电”约束的建模选择
新能源出力“可弃”这件事,在数学上处理起来比想象中麻烦。很多初学者会写一个等式:并网功率 + 弃电量 = 预测值。这本身没错,但弃电量是连续变量且非负,有时候还需要走0-1逻辑来避免“预测为0时弃电量为正”的荒谬结果。我的做法是:把弃电量定义为连续非负变量,并允许其等于预测值和并网功率的差值,再让惩罚系数足够大。只要目标函数里惩罚够高,优化器不会故意弃电,不需要额外引入整数变量,这样既保证了模型简洁,也没有陷入过量0-1变量的泥潭。
6.5 求解器不可行时的标准排查流程
当求解器返回不可行时,我的标准流程是:先把所有场景去掉,只留下确定性版本,看约束是否本身矛盾。如果确定性版本可解,就逐个场景加回来,用二分法定位是哪个场景引起的冲突。还有一种高频原因是储能的周期回位约束与末端时段出力限制冲突,比如最后两个小时热负荷很低,但热储能必须回到初值,此时很可能出现热功率不足或过剩的不可行。解决方法是给周期回位约束加一个松弛变量,并让目标函数对松弛量做高额惩罚,成本比反复手工调参数高得多。
提示:模型不可行时,不要急着改约束的数值范围,先检查是不是有两个约束在边界处交叉锁死。最常见的位置就是“储能SOC下限”与“末端SOC回位”的组合。
最后再分享两点操作体会
第一个体会是:项目启动时一定先跑通一个确定性模型,把所有设备的物理约束理顺之后,再引入不确定性建模。直接一步到位把不确定性和多设备耦合同时上马,一旦结果异常,根本分不清是场景生成的问题还是约束写错的问题。分阶段调试,每一阶段都保存一份可以运行的版本,这样回退和定位都很方便。
第二个体会是:代码里把数据和模型严格分离,会让后续的实验效率翻倍。我在scenario_gen.m里把不确定性水平做成一个参数变量,然后在main.m里用循环遍历5%、10%、15%三档误差水平,所有结果自动保存成结构体并绘图。换一个负荷剖面、调整设备容量,全程不需要碰任何模型约束代码。这听起来像是老生常谈,但实际项目里能一开始就按这个习惯做的人,真的不多。
这个项目后续还有很多可以扩展的方向,比如引入碳交易成本、加入多时间尺度协调、考虑设备随机故障等。但就目前这套“计及新能源出力不确定性的电气设备综合能源系统协同优化”Matlab实现而言,它已经足够支撑园区级能量管理的实际需求,也能够作为进一步研究的可靠基线。