上周一个做电网调度的老同学给我打电话,说他现在一听到“N-k”这三个字母就头疼:风电场装机占比过了三成之后,原来能过的N-1校验天天出问题,更别说N-2、N-k了。我跟他讲,其实把风电、光伏、光热电站一起塞进N-k安全优化调度模型里,用Matlab是可以直接求解的,只是建模思路跟传统火电调度完全不一样。
这套模型解决的核心问题,一句话说清楚:调度计划出来后,不仅要满足正常运行方式下的潮流约束,还要保证系统在预想故障集里任意k个元件失效的情况下,依然不越限、不掉负荷、不连锁过载。它最直接的价值是替调度员把“故障后的安全”这件事从校核阶段提前到决策阶段,而不是等计划出了偏差再临时补救。
这篇文章适合谁看?一是正在做电力系统调度方向毕业设计或课题研究的学生,手里有一堆风电光伏光热数据,但不知道怎么把N-k校验写进优化模型;二是地区电网里做新能源接入分析和调度计划校核的工程师,想找一套能落地参考的实现框架;三是刚接触Matlab+YALMIP这套技术栈、想在电力优化调度里快速跑通一个像样算例的同行。我会把建模逻辑、代码结构、算例设计和踩过的坑都拆开讲,尤其那些文档里不写、只有自己跑一遍才会遇到的东西,尽量一次讲透。
1. 高比例新能源下的N-k安全校验:N-1校验为什么越来越“不够用”
1.1 从N-1到N-k,安全校核到底在干什么
先补一点背景知识,方便不同基础的读者对齐。电力系统安全稳定运行,核心目标就是“单点或多点故障后,不能让系统崩溃”。传统做法是N-1校核:把系统里的某一条线路、一台变压器或一台机组逐个停掉,检查剩余系统是否依然能保持潮流不过载、电压不越限。这个“逐个停掉”的集合,就是预想故障集。
N-k比N-1更进一步,考虑的是同时失去k个元件。这里的k通常取2或更小,因为极端天气、变电站设备误动、一条输电走廊上多回线路同时跳闸等场景,现实中并不罕见。一个能扛住N-1的系统,未必能扛住N-2,尤其是两条强相关线路同时断开时,潮流会集中转移到剩余通道上,过载往往就是这样发生的。
调度模型里的N-k安全约束,本质上是把这个校核过程编码进优化问题:机组出力、启停状态这些决策变量取值,必须保证系统在预想故障集中任何一个故障场景下,都能通过潮流计算并有可行解。换句话说,你的优化目标不只是找到“正常运行最便宜”的计划,而是找到“即使坏了几个元件,也还能安全运行”的最便宜计划。
1.2 新能源接入后,故障场景的复杂度发生了质变
传统火电为主的系统,机组出力可控,预测也好做,N-k校核的边界条件是相对稳定的。但风电和光伏接入之后,情况完全不同。
风电、光伏的出力由气象条件决定,存在显著的预测误差。故障发生那一刻,风电场可能恰好处于低出力状态,也可能正好处于满发状态。前者意味着故障后系统缺少有功支撑,环网上的功率转移会更剧烈;后者意味着一旦送出通道发生N-k故障,新能源无法快速降出力,会出现严重的潮流越限。
更棘手的是,新能源场站通常分散接入配网或输电网的多个节点,潮流分布不再是传统的“电源端向负荷端单向流动”,而是多点多方向的复杂流动。同一个断面,在风大和风小的时候负荷水平完全不同,N-1校核可能白天是通的、晚上就过不去。这就是为什么我那个老同学会觉得“原来的N-1校验越来越像摆设”——不是算法错了,而是系统运行方式的变化范围太大,单一故障校核已经覆盖不住真实风险。
1.3 把N-k校验从“事后校核”变成“调度决策的一部分”
这个模型的设计思路,就是把N-k安全校验直接嵌入优化调度过程中。与你熟悉的两步走流程不同——先出计划,再做安全校核,不通过就人工调整——这个模型要求求解器一次性给出“既经济又满足所有N-k场景安全要求”的调度方案。
这样做的好处很直观:避免了“校核发现事故后反复修计划”的循环,也从源头上保证调度计划具备故障穿越能力。代价则是模型规模增大、求解时间变长,这也是后续几章要重点处理的问题。模型本身是一个典型的混合整数线性规划(MILP)问题,故障场景的引入让约束数量成倍增加,但只要结构设计合理,靠Matlab+YALMIP+商业求解器是可以稳定求解的。
2. 光热电站在安全约束调度里的特殊位置:不是“第三种新能源”那么简单
2.1 风电、光伏、光热的最大差别,不在能量来源
很多刚开始做新能源调度模型的人,习惯性地把光热电站跟风电、光伏并列,当成“三个出力曲线不同的新能源”。这个理解在数学上不算错,但会丢掉一个极其重要的物理特征——光热电站是带储热的。
风电和光伏的出力曲线基本由天气决定,你能做的只有预测、削减和在目标函数里施加惩罚。但光热电站(CSP)通过集热场、储热系统和发电动力岛的配合,可以实现“太阳能收集”和“电能输出”在时间上的解耦。中午太阳辐射强时,除了发电,多余的热量可以存进储热罐;到了晚上或光照不足时,再释放热量发电。这意味着它具备跟火电类似的可调度性,只是能量来源是太阳能而不是化石燃料。
2.2 光热电站的“三段式”结构建模要点
把光热电站拆成三段来看:
- 集热场(Solar Field,SF):收集太阳直射辐射(DNI),转化为热能。热功率大小与DNI、集热场面积和光热转换效率直接相关。
- 储热系统(TES):热罐、冷罐和换热设备组成,负责热量的存储与释放。储热容量直接决定电站的时间转移能力。
- 动力岛(Power Block,PB):汽轮机组,把热能转化为电能。发电功率由进入汽轮机的热功率决定。
建模时,核心约束是每一时刻的热功率平衡:
P_sf(t) + P_discharge(t) = P_charge(t) + P_turbine(t)其中 P_sf(t) 是集热场收集的热功率,P_discharge(t) 是储热系统释放的热功率,P_charge(t) 是充入储热系统的热功率,P_turbine(t) 是送往汽轮机的热功率。
储热系统的能量平衡也要写进去:
E_tes(t+1) = E_tes(t) + eta_charge*P_charge(t) - P_discharge(t)/eta_discharge - loss_tes储热罐的热损失通常按每小时千分之几到百分之一计算,别小看这个系数,在长时间尺度优化里它会显著影响储热策略的走向。再配合储热罐容量上下限、汽轮机出力上下限和爬坡约束,光热电站的调度模型就成型了。
2.3 光热如何成为N-k场景下的“快速备用”
传统调度里,N-k故障发生后,系统主要依靠火电机组的快速爬坡来补功率缺口。但火电爬坡速率有限,而且受最小技术出力限制,调节空间不一定够。光热电站在这个场景下有个天然优势:它的汽轮机组可以在储热系统支持下比较快速地调整出力,响应特性接近火电,又没有燃料成本压力。
更关键的是,光热电站的出力不像风电光伏那样随天气“裸奔”。在调度计划层面,你可以把储热罐里存的热量当成一个“虚拟备用容量”,在N-k场景下释放出来补缺口。这就让光热电站从一个单纯的新能源发电单元,变成了安全约束下参与系统调节的灵活性资源。
2.4 三类电源在安全约束调度里的角色差异
| 电源类型 | 出力可控性 | 预测误差 | 故障后响应能力 | 调度模型中的角色 |
|---|---|---|---|---|
| 风电 | 基本不可控 | 较大 | 几乎无 | 可削减的出力源 |
| 光伏 | 基本不可控 | 较大 | 几乎无 | 可削减的出力源 |
| 光热 | 可控(受储热约束) | 较小(DNI可预测) | 较强 | 可参与平衡的可调度电源 |
这个表格我建议在写论文或报告时直接参考——它能帮你快速解释清楚“为什么这个模型要把三类电源分开建模”。顺便提一句,光伏逆变器层面的MPPT控制、Boost变换器这些,属于设备级或场站级仿真(Simulink里常做)的问题,而这里讨论的是系统级调度模型,两者研究对象不同,不要把层次搞混。
3. N-k约束怎么落到优化模型:从穷举到迭代校验的工程路径
3.1 先把数学形式写出来
N-k安全约束的数学本质是一个鲁棒约束。调度决策变量x(机组出力、开停机、新能源削减量等)必须满足:对于预想故障集中的每一个故障场景s,都存在一个对应的调整变量y_s(比如故障后的机组再调度量、负荷削减量),使得故障后的潮流方程、支路潮流极限、功率平衡都能满足。
写成紧凑形式就是:
min f(x) s.t. 正常运行约束 g(x, 0) ≤ 0 对任意 s ∈ S:存在 y_s,使得 g_s(x, y_s) ≤ 0这里的S就是预想故障集合,规模大小直接决定了问题的规模。
3.2 穷举所有故障组合?组合爆炸会教做人
最朴素的想法是枚举所有可能的N-k故障组合,每个组合加一组约束。但很快你就会发现这条路走不通。假设一个中等规模的系统有100条支路和30台机组,N-2场景的组合数是C(130,2)=8385个。每个故障场景在MILP里对应一组直流潮流约束和几十个变量,全部一次性加进去,约束数量轻松超过十万,求解器基本跑不动。
如果k取3,组合数直接到百万级,根本不现实。所以工程上必须想办法收缩故障集,或者采用迭代求解框架,把N-k校验放在循环里逐步完成。
3.3 关键故障集:把一个系统工程问题先降维
实际操作中,我会先做关键故障集筛选,而不是把全部组合塞进模型。筛选维度主要有三个:
- 电气位置相关:新能源集中接入节点附近的线路、升压变、送出通道。这些元件一旦故障,对新能源功率外送影响最大。
- 潮流转移指标:用线路开断分布因子(LODF)或功率传输分布因子(PTDF)评估某个故障对剩余支路的潮流冲击程度。LODF大的故障对,通常是高风险故障。
- 历史故障数据:如果系统有运维历史记录,就统计哪些设备、哪个走廊发生故障的频率高。运维数据可以直接指导故障集筛选。
筛选之后,S的规模会从数万降到几百甚至几十。这个“降维”的操作提升了计算效率,也不会因为少算某些低风险组合而明显影响安全水平。需要说明的是,关键故障集的选取本身就是一套方法论,不同系统、不同安全标准下选择不同,实际工程里往往要结合运行经验和风险偏好来决定。
3.4 迭代校验:Benders分解思想在N-k约束里的应用
另一个更普适的做法是迭代校验,本质上就是Benders分解。流程如下:
- 主问题只包含正常运行约束和关键故障集中已加入的约束,求解得到一个调度计划x。
- 把x固定,对故障集中的每一个场景s做可行性校验(子问题)。这个子问题通常是线性或混合整数规模很小的优化问题,求解很快。
- 如果某个场景s不可行,说明当前的调度计划扛不住这个故障,就生成一个关于主变量的可行性割平面,加回主问题重新求解。
- 重复2和3,直到所有故障场景都通过校验,得到最终调度方案。
这个框架的优点是每次主问题规模可控,不需要把所有故障约束一次性塞进MILP。代价是需要多次求解主问题,迭代次数取决于割平面的质量和故障集的更新策略。我实际跑下来的经验是,大多数IEEE标准测试系统上,10到20次迭代就能收敛,单次主问题求解时间是秒级到分钟级,整体完全可以接受。
3.5 直流潮流模型在故障场景里的处理
N-k安全校验里,潮流模型我建议用直流潮流(DC power flow)。交流潮流(AC power flow)是非线性约束,跟整数变量放在一起就是MINLP,求解困难且稳定,在日前调度这种需要快速出结果的场景里性价比不高。直流潮流的精度对大多数安全校核问题足够,误差通常在几个百分点内,而且能直接嵌进MILP求解器得到全局最优解。
直流潮流的写法是:
P_ij = (theta_i - theta_j) / X_ij节点功率平衡为:
B * theta = P_inj其中B是节点电纳矩阵,theta是节点相角向量,P_inj是节点注入功率。N-k故障发生后,把故障支路对应的电纳项从B矩阵中移除,重新求解潮流即可。这里有个细节要注意:如果故障导致系统解列成多个孤岛,B矩阵会变得奇异,需要分别对每个孤岛做功率平衡校验,后面第6章我会专门讲这个坑。
4. Matlab+YALMIP实现:变量、约束与求解框架
4.1 决策变量和目标函数设计
模型里的决策变量我分组列一下,方便你在写代码时照葫芦画瓢:
| 变量组 | 含义 | YALMIP类型 |
|---|---|---|
| P_g(t,i) | 火电机组有功出力 | sdpvar |
| u_g(t,i) | 火电机组启停状态 | binvar |
| P_w(t,k) | 风电场实际出力 | sdpvar |
| P_pv(t,k) | 光伏电站实际出力 | sdpvar |
| P_csp(t,k) | 光热电站发电出力 | sdpvar |
| P_charge/P_discharge(t,k) | 储热系统充/放热功率 | sdpvar |
| E_tes(t,k) | 储热罐储热量 | sdpvar |
目标函数建议这样写:
J = sum(火电燃料成本) + sum(启停成本) + sum(弃风弃光惩罚) + sum(光热运行成本)其中弃风弃光惩罚系数要设得比火电边际成本高,才能让模型优先用新能源出力;但又不能高到离谱,否则会扭曲经济性。我常用的做法是取火电边际成本的1.2到1.5倍,具体数值根据数据特性调整。
4.2 常规约束与N-k校验约束的写法
常规约束包括系统功率平衡、机组出力上下限、爬坡约束、最小启停时间约束、旋转备用约束。这些每个做调度的人都很熟,不再展开。重点说N-k校验约束。
YALMIP里加故障场景约束,最直接的方法是循环遍历故障集,每遇到一个故障场景就单独添加一组直流潮流约束。但这样写多了之后,模型文件会变得很长。我建议把故障场景的处理封装成一个函数,输入故障场景编号,输出该场景下的潮流约束集合,主程序里循环调用即可。
需要注意,直流潮流约束在YALMIP里要避免用矩阵求逆的方式去构造,那样会引入非线性。正确做法是先把节点电纳矩阵B_s算好(已经剔除故障支路),然后用线性等式约束B_s * theta == P_inj来写潮流方程。
4.3 核心代码骨架
下面给一段建模思路的示意代码,目的是让你理解整体骨架,不是完整的可运行工程,实际使用需要按你的系统数据和求解器做适配:
% 初始化YALMIP yalmip('clear') % 决策变量 P_g = sdpvar(T, Ng, 'full'); % 火电出力 u_g = binvar(T, Ng, 'full'); % 火电启停 P_w = sdpvar(T, Nw, 'full'); % 风电出力 P_pv = sdpvar(T, Npv, 'full'); % 光伏出力 P_csp = sdpvar(T, Ncsp, 'full'); % 光热发电出力 P_ch = sdpvar(T, Ncsp, 'full'); % 储热充热 P_dis = sdpvar(T, Ncsp, 'full'); % 储热放热 E_tes = sdpvar(T, Ncsp, 'full'); % 储热罐储能量 % 目标函数 Objective = sum(sum(coal_cost(P_g))) + sum(sum(start_cost(u_g))) ... + penalty_w * sum(sum(P_w_forecast - P_w)) ... + penalty_pv * sum(sum(P_pv_forecast - P_pv)) ... + sum(sum(csp_oper_cost(P_csp))); % 常规约束 Constraints = []; for t = 1:T Constraints = [Constraints, sum(P_g(t,:)) + sum(P_w(t,:)) + sum(P_pv(t,:)) ... + sum(P_csp(t,:)) == load(t)]; % 机组出力上下限、爬坡约束等... end % N-k安全约束(迭代框架中,每次只加当前割平面/故障集) for s = 1:N_scenario Constraints = [Constraints, nk_security_constraints(x, scenario_list(s))]; end % 求解 ops = sdpsettings('solver', 'gurobi', 'verbose', 2, 'mipgap', 0.005); optimize(Constraints, Objective, ops);这段代码里nk_security_constraints就是你要自己实现的核心函数:给定故障场景,构造对应的直流潮流约束、支路潮流极限约束和功率平衡约束。建议单独写一个函数文件,后面迭代调用会方便很多。
4.4 求解器配置和性能调优
求解器我推荐Gurobi或CPLEX,两个都能跟YALMIP良好配合。如果你用的是新版Matlab,建议把YALMIP更新到最新版本,否则可能因为API不匹配报各种莫名其妙的错误。MIPGap设到0.1%到0.5%之间比较稳妥,既能保证解的质量,又不至于让求解器在细枝末节上死磕。迭代校验框架里,每次主问题求解建议开启warm start,把上一次的解作为初值传给求解器,迭代速度会明显加快。
5. 算例设计:修改版IEEE-30节点系统上的成本与安全权衡
5.1 测试系统怎么搭
算例我建议用修改版IEEE-30节点系统,这是学术界和工程验证里最常用的测试平台之一。原始系统里全是火电机组,需要做三处修改:
- 在节点5和节点8分别接入一个风电场和一个光伏电站,装机容量取系统峰值负荷的20%和15%左右。
- 在节点11接入一个光热电站,配置6小时储热系统,装机容量取10%左右。
- 风电和光伏的预测出力曲线用典型日数据,光热电站的DNI数据用同一天的气象数据。
风电功率、光伏功率和DNI的数据最好来自同一时间段,保持时序相关性。这一点容易忽略——如果你把风电数据取冬天、光伏数据取夏天拼在一起,算出来的结果会严重失真。
5.2 四组算例对比
我建议跑四组对比:
- Case A:无安全约束,纯经济调度。
- Case B:只加N-1校验。
- Case C:加N-2校验,故障集为预先筛选的30个关键场景。
- Case D:N-2校验加上光热电站参与电网调节(允许光热在故障后释放储热提供备用)。
四组的运行成本、弃风弃光量、计算时间和是否通过安全校验,整理成下表才有说服力:
| 算例 | 总运行成本 | 弃风弃光量 | 计算时间 | N-2越限次数 |
|---|---|---|---|---|
| Case A | 基准 | 较高 | 最快 | 若干 |
| Case B | 略升 | 略降 | 较快 | 仍有 |
| Case C | 明显上升 | 明显下降 | 较慢 | 0 |
| Case D | 比C低 | 比C低 | 较慢 | 0 |
真实数值会因数据和参数而异,但这个趋势是稳定的。核心结论是:安全约束一定会抬高经济成本,而光热电站的储热能力能把这个成本增量打下来一部分。
5.3 结果解读:安全约束的成本代价和光热的缓解作用
Case A到Case C的成本上升,本质上就是安全裕度的“保费”。你花钱买的是故障发生后系统依然不越限的能力。这个保费高不高,取决于故障集选的凶不凶、系统结构裕度大不大。
Case D的结果则有额外的启发:光热电站的储热系统在正常调度阶段可能只是“把中午的热量挪到晚上用”,但在安全约束视角下,它相当于给系统增加了一笔随时可动用的快速调节资源。同样一个储热罐,在调度模型里同时扮演了“能量时移”和“安全备用”两个角色。这种双重属性,是风电和光伏无论如何也提供不了的。
这里有个判断值得强调:如果系统里接入足够大容量的光热电站,N-2校验的附加成本有可能降到很低,甚至接近N-1水平。这对高比例新能源地区的电网规划有直接的参考意义——与其堆砌更多线路和备用电站,不如在规划阶段就把光热的储热能力算进安全账本里。
6. 代码实现中踩过的坑和边界条件
6.1 求解器版本与YALMIP的兼容性问题
第一个坑永远是版本兼容。新版Matlab配旧版YALMIP,会出现诸如“AddParam”找不到、求解器求解失败但报错信息模糊等问题。我的建议是直接到GitHub拉YALMIP的最新release,而不是下载网上的老版本包。Gurobi和CPLEX的版本也要跟Matlab匹配,安装后记得用yalmiptest()命令验证所有求解器都能被正常识别。
6.2 迭代校验不收敛:多半是割平面太弱
迭代框架不收敛的常见原因,是生成的可行性割平面太弱,没有有效排除导致故障场景不可行的调度方案。我在实现时遇到的典型问题是:子问题不可行时,直接简单粗暴地返回“不行”,但生成的割平面没有携带足够的信息。解决办法是用不可行子问题的对偶乘子构造割平面,或者干脆把子问题的违规量(比如支路过载量)作为新约束加入主问题,效果会好很多。
6.3 故障后孤岛导致的B矩阵奇异
N-2场景最阴险的地方在于,两条线路同时断开可能把系统切成两个或多个孤岛。直流潮流方程里B矩阵是奇异的,YALMIP求解会直接报错。处理方式是检测故障后的拓扑连通性:如果系统解列成岛,就分别对每个岛做功率平衡校验——岛内发电和负荷必须平衡,否则该故障场景直接判为不可行。这个检测逻辑可以写成一个单独的函数,用图遍历算法判断连通分量。
6.4 新能源数据处理不当带来的过拟合
风电、光伏的预测出力曲线如果只取一天的理想数据,模型训练出来的调度策略会过拟合这个样本。更稳妥的做法是取多个典型日场景,或者用场景削减技术(比如基于概率距离的同步回代削减)生成代表性场景集。我之前吃过一次亏:用了单日高风速数据,结果风电出力削减量被优化成了零,看起来模型“充分利用了新能源”,但换一天数据立刻失效。
6.5 代码性能太慢的优化经验
最后说两个提速度的经验。第一,尽量避免在YALMIP里用大量双重for循环逐个加约束,能把约束写成矩阵形式就写成矩阵形式,YALMIP对批量约束的编译效率远高于逐条添加。第二,迭代求解时每次主问题求解完,把当前解作为下次迭代的初始解传给求解器(Gurobi的MIP start功能),能显著减少求解器重新搜索的时间。我实际测试下来,两步改造加起来能把总求解时间压缩一半以上。
7. 个人体会与可扩展方向
我自己把这套模型完整跑通之后,最大的收获不是那一堆优化结果,而是对“安全约束到底怎么吃掉经济性”这件事有了直觉的量化认知。一次N-2校验,短期看是让运行成本上升了几个百分点,长期看它改变的不只是调度计划,还包括你对系统备用配置、故障集选择、新能源接入比例的一系列判断。这个模型给我最大的启发是:光热电站不应该只当作一个“清洁电源”来规划,它的储热系统在更高安全标准下反而可能成为性价比最高的调节资源。
如果后续想继续扩展,我建议优先做两件事。一是把关键故障集的筛选从离线静态改为在线滚动更新,让调度模型能根据近期的运行数据和气象预报动态调整故障集,这样安全裕度和经济性会更贴近实际。二是考虑把光热电站的储热容量作为决策变量的一部分,在规划层面对“建多大储热罐最划算”给出答案,这比单纯在调度层面调参数更有工程价值。这两个方向都有不少论文在做,代码框架完全可以在现有模型上继续加,不需要推倒重来。