简介:面向智能微电网多目标优化研究的一份MATLAB代码资源包,主要服务电力系统、电气工程及人工智能方向的学生与研究人员。资源围绕粒子群优化算法在微电网中的调度与优化场景,同时包含遗传算法、经济调度等辅助程序,可帮助理解多目标适应度函数设计、约束处理与迭代寻优流程。包内共16个文件,以10个脚本文件为核心,另有4个数据文件(光伏、风电、典型日负荷、电价)和2个仿真结果图,整体仅35KB,结构清晰,适合直接用MATLAB打开运行。脚本覆盖初始化、交叉、变异、适应度评估、经济调度等环节,数据文件为仿真提供边界条件,图文件则展示电价变化与出力结果。已有253人学习下载。通过多个核心程序与配套输入数据,读者可以快速搭建微电网多目标优化实验框架,复现出力曲线,便于开展算法对比与课程设计。
1. 智能微电网多目标优化:神经网络的角色不是替代,而是前置与加速
一个小型园区微电网,光伏、储能、柴油机并在一起,运行人员要排未来一天的调度计划:既要压购电成本,又要压碳排放,还要保证别动不动就切负荷。这三个目标互相掐架——多发电动机组省钱但排放大,多从电网买电省事但费用和碳排一起涨。传统的做法是加权求和,可权重怎么定、目标量纲怎么统一,本身就够吵一阵子。标题里这套“基于神经网络的智能微电网多目标优化算法”解决的是更具体的一件事:用神经网络把光伏出力、负荷曲线这类未来时段的参数先预测出来,再交给多目标优化算法在可行域里搜出一整条帕累托前沿,让运行人员按工况挑解,而不是拍脑袋定权重。适合正在做微电网能量管理系统(EMS)、想把预测模块和调度优化模块真正接起来的工程师和研究生。
2. 从拿到 rar 到建出优化模型:目标函数、约束与数据流
2.1 先拆压缩包:校验、文件清单与目录规划
不管这个 rar 是从内网还是从协作群里流转过来的,第一件事不是双击解压,而是先做校验。压缩包在网络传输中损坏的概率比想象中高,解压到一半报“CRC 失败”时,你根本分不清是数据坏了还是代码本身有问题。我一般会在类 Unix 环境下先看哈希,再列文件清单,最后才真正解压:
sha256sum Microgrid_RNN_MOEA.rar unrar l Microgrid_RNN_MOEA.rar mkdir -p microgrid/{data,models,scripts,results} unrar x Microgrid_RNN_MOEA.rar -d microgrid/sha256sum拿到的是整个压缩包的摘要,下载完成后和来源给的哈希比对,能确认文件没在中途被截断;unrar l只列清单不解压,让你先看清里面是数据、代码还是文档,有没有缺失的目录项;mkdir这一步很多人会跳过,但多目标优化的代码跑一轮就要写一大批结果文件,没有固定目录,后处理脚本会变得非常痛苦。解压参数-d指定目标目录,避免文件散落一地。
这里有一个人人都可能撞上的坑:某些压缩包实际是“伪加密”——文件头里标记了加密位,但数据本身并没有真正加密。表现就是解压时弹密码框,可来源明明说无密码。这种情况不需要去找什么密码移除工具,直接用正规解压软件换一种解压方式试,或者换官方 RAR 工具打开,多数伪加密包直接就出来了。反过来,如果真的是加密包,那就老老实实找作者要密码,不要浪费时间在旁门左道上——哈希校验既能防损坏,也能防拿错文件。
2.2 三个目标函数与四个约束的数学表达
拆完包先别急着跑代码,你手上拿到的东西大概率是一堆历史负荷数据、光伏出力数据和一个优化器脚本。要把优化器用在自己场景里,必须先看懂它的目标函数和约束是怎么写的。微电网多目标优化最常见的是三个目标,我做了一张表贴在项目文档里:
| 目标 | 含义 | 典型表达式 |
|---|---|---|
| 运行成本 | 柴油机燃料 + 购电费用 + 储能退化折算 | (f_1=\sum_{t}(aP_{dg,t}^2+bP_{dg,t}+c+c_{buy}P_{grid,t})) |
| 碳排放 | 柴油机与购电对应的排放量 | (f_2=\sum_{t}(e_{dg}P_{dg,t}+e_{grid}P_{grid,t})) |
| 失负荷率 | 缺供功率占总负荷的比例 | (f_3=\frac{1}{T}\sum_{t}\max(0,\ P_{load,t}-P_{pv,t}-P_{dg,t}-P_{bess,t}-P_{grid,t})/P_{load,t}) |
这三个目标里,成本是经济账,碳排放是环境账,失负荷率是可靠性账。注意第三个目标不能直接写成硬约束“必须保证功率平衡”,因为微电网允许在极端工况下切部分负荷,你要做的是在目标里惩罚它,让优化器在成本和可靠性之间自己权衡。
约束条件里最核心的是这么几条:每个时段的功率平衡方程(发电加购电等于负荷加储能充电)、储能 SOC 的递推公式与上下限、柴油机爬坡约束,以及联络线功率上限。我见过不少初学者把功率平衡写成等式硬约束,结果多目标算法里很难找到严格满足等式的个体,种群前期直接全部淘汰。工程上的做法是把它转成不等式约束,允许小范围内有偏差,偏差进罚函数。
2.3 用 numpy 把目标函数写成可计算代码
数学表达式要落到代码上才能被 NSGA-II 这类算法调用。决策变量一般是两部分:每个时段的柴油机出力 (P_{dg,t}) 和储能充放电功率 (P_{bess,t}),未来 96 个时段就是 192 维的搜索空间。光伏出力和负荷是预测值,作为已知参数传进来。下面是我常用的一种目标函数向量化写法:
import numpy as np T = 96 P_load = load_profile[:T] # 预测负荷,长度 96 P_pv = pv_profile[:T] # 预测光伏,长度 96 def evaluate(x, P_load, P_pv): P_dg = x[:T] # 柴油机各时段出力 P_bess = x[T:2*T] # 储能充放电,正值放电 P_grid = P_load - P_pv - P_dg - P_bess # 联络线功率由功率平衡推出 cost = np.sum(a * P_dg**2 + b * P_dg + c + c_buy * np.maximum(P_grid, 0)) co2 = np.sum(e_dg * P_dg + e_grid * np.maximum(P_grid, 0)) loss = np.mean(np.maximum(P_load - P_pv - P_dg - P_bess - P_grid, 0) / P_load) return np.array([cost, co2, loss])这里把联络线功率 (P_{grid}) 当成被推出的量,而不是独立决策变量,能直接把决策空间降一维,也让功率平衡约束天然成立。(a,b,c) 是柴油机燃料成本二次方程的系数,(c_{buy}) 是购电电价,(e_{dg}) 和 (e_{grid}) 是对应的碳排放因子。注意 (P_{bess}) 的正负号定义:正值表示放电,负值表示充电,后续在 SOC 约束里也要按这个约定写递推式,否则储能会一边充电一边给 SOC 减分。
SOC 约束我一般单独写一个violation(x)函数,返回所有约束违反量的平方和,让优化器去最小化。因为 NSGA-II 处理约束的方式通常是“约束支配”,而不是直接把不可行解丢掉——丢掉会让种群多样性崩掉,尤其在高维决策空间里,可行域可能只有一小块。用罚函数把违反量加进个体比较里,前期能保住种群规模,后期靠可行解慢慢把不可行解挤掉。
3. 用 BP / LSTM 把光伏与负荷预测接进优化器:最小可运行版本
3.1 神经网络在微电网里的三种嵌入方式
拿到优化器代码后,很多人会疑惑:标题里说“基于神经网络”,可 NSGA-II 本身跟神经网络没啥关系。实际上神经网络在这个系统里通常有三种角色,选错角色会让后面所有调参都白费。
| 嵌入方式 | 适用场景 | 可解释性 | 落地成本 |
|---|---|---|---|
| 预测模块 | 光伏/负荷未来时序预测,输出给优化器当参数 | 中 | 低,数据够就能训 |
| 代理模型 | 替代潮流计算、电池寿命评估等耗时的适应度评价 | 低 | 高,需要生成大量仿真样本 |
| 策略网络 | 用强化学习直接输出调度动作 | 低 | 很高,样本效率差,收敛难 |
工程上最常见、最容易复现的是第一种:BP 或 LSTM 负责预测未来 24~96 个时段的光伏出力和负荷,预测值作为已知参数传给多目标优化器。BP 本质上是前馈神经网络,结构简单,数据量小时能用;但光伏出力是强时序序列,读数之间有很强的自相关性,纯前馈网络会把这些时序关系当噪声丢掉。LSTM 这类循环网络天然适合序列建模,代价是训练更慢、超参更多。如果你的数据只有几千条记录,我建议先上 BP,训不出来再上 LSTM,不要一上来就追求复杂结构。
代理模型那条路听着高级,但实际落地非常折腾:你要先用仿真软件生成上千组样本,训练一个 MLP 去拟合潮流结果,然后还要验证代理模型在边界区域的误差。这一步做不好,优化器会在代理模型的误差低谷里找到一堆“假最优”。除非你的潮流计算单次超过几十毫秒,否则不值得。
3.2 用 Keras 训练一个 LSTM 光伏出力预测器
我通常用 Keras 搭一个两层的 LSTM 做光伏预测,窗口取过去一周的同一时段数据,输出未来 24 点。一个最小可运行版本长这样:
from tensorflow import keras from tensorflow.keras.layers import LSTM, Dense from sklearn.preprocessing import MinMaxScaler # data: 历史光伏出力序列,shape=(N,) look_back = 24 * 7 # 前一周的 15 分钟数据 horizon = 24 # 预测未来 24 个时段 X, y = [], [] for i in range(look_back, len(data) - horizon): X.append(data[i - look_back:i]) y.append(data[i:i + horizon]) X = np.array(X).reshape(-1, look_back, 1) y = np.array(y) model = keras.Sequential([ LSTM(64, return_sequences=True, input_shape=(look_back, 1)), LSTM(32), Dense(horizon) ]) model.compile(optimizer='adam', loss='mae') model.fit(X, y, epochs=50, batch_size=64, validation_split=0.1)两个 LSTM 层的结构是“深层特征提取 + 压缩”,第一层return_sequences=True是为了把完整序列信息传给第二层。损失函数我不用mse而用mae,原因是光伏曲线有大量零值和尖峰,mse会被少数尖峰主导,模型会为了压低峰值误差而牺牲平段精度;mae对异常值更稳健。训练前记得做归一化,光伏出力归一到 [0,1] 区间,后面反归一化时才能保证优化器拿到原始量纲的功率值。
训练收敛以后,核心动作是保存归一化器和模型参数。这个细节几乎每个接优化器的人都会踩:模型输出的是归一化后的数值,不反归一化直接塞进目标函数,功率平衡和 SOC 约束全乱。下一小节专门说对齐问题。
3.3 预测值如何传入 NSGA-II 而不引入偏差
预测模型训好了,和优化器的对接有两条铁律:一是时段对齐,二是滚动刷新。时段对齐指的是预测输出的 24 个点必须严格对应优化模型里 T=24 或 T=96 的时段索引,差一个点,整个调度计划就是错位的。写代码时我会把预测起始时间当作一个显式变量传给优化器入口,而不是让优化器自己从文件头读数据。
predict_start = current_step # 当前时刻对应优化起始时段 pred_pv = model.predict(last_week_win) # shape=(1, horizon) pred_pv = scaler.inverse_transform(pred_pv) # 传入优化器,强制从 predict_start 时段开始 opt_inputs = { "P_pv": pred_pv.flatten(), "P_load": pred_load.flatten(), "start_idx": predict_start }滚动刷新是更重要的工程观念:光伏预测不是做一次管一天,而是每隔 4~6 小时用最新实测数据重新预测一次,再触发一次优化。微电网场景里天气变化会让几小时前的预测完全失效,不滚动更新的系统就是拿昨天的天气预报做今天的调度决策。这套“预测-优化-执行-再预测”的闭环,本质上就是模型预测控制(MPC)的思路。
对接时还有一个容易翻车的细节:预测模型的输入窗口(比如过去 168 个时段)和优化窗口(未来 24/96 时段)的采样间隔必须一致。如果历史数据是 15 分钟一个点,预测输出也是 15 分钟一个点,结果你优化模型里一个时段代表 1 小时,功率数值就得做累加或平均,这一步做错会让储能 SOC 曲线在边界处凭空多出一大截能量。
4. NSGA-II 多目标优化核心实现:非支配排序、拥挤度与关键参数
4.1 非支配排序与拥挤度距离的计算逻辑
多目标优化算法里,NSGA-II 至今仍是工程界最稳的选择。它的核心思路是分层:先把种群分成好几层“非支配前沿”——第一层的解不被任何其他解支配,第二层的解只被第一层支配,以此类推。判断“支配”的逻辑很简单:一个解在所有目标上都不差,且至少有一个目标严格更好。
def dominates(a, b): return all(x <= y for x, y in zip(a, b)) and any(x < y for x, y in zip(a, b)) def fast_non_dominated_sort(pop): fronts = [[]] for p in pop: p.dominated_count = 0 p.dominated_solutions = [] for q in pop: if dominates(p.fitness, q.fitness): p.dominated_solutions.append(q) elif dominates(q.fitness, p.fitness): p.dominated_count += 1 if p.dominated_count == 0: fronts[0].append(p) # 逐层生成后续 front rank = 0 while fronts[rank]: next_front = [] for p in fronts[rank]: for q in p.dominated_solutions: q.dominated_count -= 1 if q.dominated_count == 0: next_front.append(q) rank += 1 fronts.append(next_front) return fronts[:-1]这段代码的时间复杂度是 (O(MN^2)),M 是目标数,N 是种群规模。种群 200 时毫秒级完成,但如果你的种群加到 2000 并且每代都全量排序,开销会明显上升。工程上常见的加速做法是在评估目标函数时把结果缓存起来,避免同一组决策变量被重复计算。
拥挤度距离是 NSGA-II 保持多样性的关键:同一前沿里的解按每个目标排序,边界解给无穷大距离,内部解的距离等于相邻两个解在每个目标上的归一化差值之和。这个值越大,说明这个解周围越空旷,应该优先保留。调试时最容易发现的问题就是拥挤度计算用错方向,症状是帕累托前沿挤到某一个目标的最优角上,看似收敛实则多样性崩了——这个坑我放在第 5 章细说。
4.2 锦标赛选择、SBX 交叉与多项式变异的实现
有了非支配排序和拥挤度距离,NSGA-II 的主循环就是标准的“选-交-变-留”四步。每一代先做二元锦标赛选择:随机抽两个个体,先比非支配层级,层级小的赢;同层则比拥挤度,拥挤度大的赢。然后对选中的父代做模拟二进制交叉(SBX)和多项式变异,生成子代,最后把父代和子代合并,按“非支配层级优先、同层拥挤度优先”的规则截断到种群规模。
def tournament_select(pop, fitness_rank, crowding): i, j = np.random.randint(0, len(pop), 2) if fitness_rank[i] < fitness_rank[j]: return pop[i] elif fitness_rank[i] > fitness_rank[j]: return pop[j] else: return pop[i] if crowding[i] > crowding[j] else pop[j] def sbx_crossover(p1, p2, eta_c=20): """模拟二进制交叉,eta_c 控制子代与父代的接近程度""" mask = np.random.rand(len(p1)) < 0.5 beta = np.where(mask, 1.0, 0.0) # 只对部分决策变量做交叉 # 标准 SBX 公式:beta_u 按均匀分布生成,再映射到 beta 分布 u = np.random.rand(len(p1)) beta = np.where(u <= 0.5, (2 * u) ** (1 / (eta_c + 1)), 1 / (2 * (1 - u)) ** (1 / (eta_c + 1))) c1 = 0.5 * ((p1 + p2) - beta * np.abs(p2 - p1)) c2 = 0.5 * ((p1 + p2) + beta * np.abs(p2 - p1)) return c1, c2SBX 交叉的eta_c分布指数决定子代离父代的远近:eta_c越大,子代越贴近父代,搜索越局部;eta_c越小,子代偏离越多,探索能力越强。微电网调度这类连续变量问题,我一般设 20,再配合多项式变异做局部扰动。注意决策变量有上界下界(柴油机出力上限、储能功率上限),交叉和变异后必须裁剪回边界,否则目标函数会算出负功率这种物理上不存在的解。
多项式变异的实现也不复杂:对每个决策变量以变异概率触发扰动,扰动幅度同样受分布指数eta_m控制。不要为了追求“看起来学了很多新东西”而换用别的变异算子——多项式变异在边界约束处理上最省心,生成的子代不会像高斯变异那样频繁越界。
4.3 参数怎么设:种群、代数、交叉率与停止条件
NSGA-II 这层参数直接决定解的质量。我整理了一份常用的起始参数,适合 96 时段、192 维决策变量的问题:
| 参数 | 建议值 | 说明 |
|---|---|---|
| 种群规模 | 200~300 | 决策变量高维时不能低于 200,否则前沿覆盖不足 |
| 迭代代数 | 200~500 | 看收敛曲线,而不是拍脑袋定死 |
| 交叉概率 | 0.9 | SBX 交叉是主要搜索手段,概率要足够高 |
| 变异概率 | 1 / 决策变量数 | 保证平均每个个体有 1 个变量被扰动 |
| 分布指数 | eta_c=20, eta_m=20 | 连续变量问题的通用起步值 |
注意变异概率不是 0.1 这种拍脑袋的数,而是1/n,n 是决策变量个数。192 维的问题,变异概率大约 0.005,每个个体平均有约 1 个变量发生变异,这样既保证扰动又不会把优秀解打得太散。交叉概率 0.9 意味着绝大多数个体都要参与交叉。
停止条件比代数更重要。光跑固定代数,经常会发现 200 代后前沿还在缓慢移动。我一般用超体积(HV)作为收敛指示器:每 10 代算一次 HV,如果连续 30 代提升量小于一个阈值就提前停止。
hv_history = [] patience = 30 for gen in range(max_gen): # ... 进化主循环 ... hv = compute_hypervolume(current_front, ref_point) hv_history.append(hv) if len(hv_history) > patience and \ np.max(hv_history[-patience:]) - hv_history[-1] < 1e-4: break这里有个经验认知:种群规模和代数不是越大越好。种群 500、代数 1000 的组合会让单次实验跑十几分钟,而微电网调度问题通常需要 Monte Carlo 跑多组天气场景、多组负荷曲线,单位实验时间超过三分钟就很难批量调参。我的习惯是先小种群快跑定位参数问题,再放大种群做最终精算。
5. 常见问题与排查:数据泄漏、拥挤度方向、压缩包伪加密
5.1 解压后报文件缺失或校验值对不上
现象:rar 包解压到一半报错,或者解压完了发现少了一个关键数据文件,代码一跑就FileNotFoundError。有时候压缩包在传输过程中丢了一部分数据,但操作系统解压时没完全报错,只给你留下一堆损坏的文件。
原因:大概率是源文件本身传输出问题,或者被下载工具截断。这里要强调一下:不要用浏览器那种“点一下就完事”的内置下载器去拉大压缩包,尤其是在网络状态不佳的环境里。
解决:先回到源文件重新拿一次哈希值,对比自己算出来的sha256sum,对不上就果断重传重下。下载后先unrar l看清单,核对里面是不是包含data/、models/、scripts/这几个预期目录。伪加密的情况前面说过,文件头标记被置位但数据未加密,表现为总弹密码框——换个解压方式或找可靠来源重下,不要碰任何密码破解类的工具,那不是工程问题,是合规风险。
5.2 预测归一化和反归一化搞反,储能 SOC 直接越界
现象:优化器跑出来的储能 SOC 曲线在一天之内冲到 120% 或者跌到 -20%,功率平衡算出来的联络线功率也忽大忽小,看起来完全不像一个物理上可行的调度计划。
原因:预测模块里做了 MinMaxScaler 归一化,预测输出是 [0,1] 区间的值;但目标函数和约束里用的是原始千瓦和千瓦时量纲。归一化后的“1.0”被当成 1 kW 传进去,而实际可能是 100 kW 的装机容量。这种量纲错乱会在 SOC 递推里被放大,因为 SOC 是积分量,每一时段的误差会累积到后续所有时段。
解决:在预测值和优化器之间加一道显式的反归一化,并且把边界值也拉回来。一个更稳健的做法是把归一化器也传给优化器入口,在目标函数内部统一转换,避免多次复制粘贴导致不一致:
def evaluate_with_unscale(x_scaled, P_load_raw, P_pv_raw, scaler): x = scaler.inverse_transform(x_scaled.reshape(1, -1)).flatten() return evaluate(x, P_load_raw, P_pv_raw)调试时先把x打印出来,肉眼看一下量纲是否符合预期,再做长时段的 SOC 校验。这一步能拦住 90% 的“优化结果不可用”问题。
5.3 拥挤度距离方向算反,帕累托前沿挤到一角
现象:算法收敛后,帕累托前沿的所有点全堆在“运行成本最低”那个角上,碳排放和可靠性目标几乎没有变化。看起来好像找到了最优,但画出来根本不是一条完整的前沿,而是一个点附近的一簇解。
原因:拥挤度距离计算时把目标函数的方向处理错了。比如碳排放目标你写成越大越坏,但代码里用的是“目标值越小距离越大”的排序逻辑,或者多个目标里有一两个没有统一成最小化方向。拥挤度距离依赖每个目标上的排序顺序,方向不一致时距离计算会乱套。
解决:在代码入口统一把所有目标转成最小化形式。成本、碳排放本来就是越小越好,失负荷率也是;如果有人把某个目标写成“可靠性最大化”,那要在计算拥挤度之前取负号或取倒数。另一个细节是拥挤度计算前对每个目标做归一化,因为成本可能是几十万元量级,失负荷率是 0.01 量级,不归一化时高量纲目标会主导距离,低量纲目标的多样性直接被忽略。
5.4 训练集和测试集随机切分,造成“好看但不可用”的预测
现象:LSTM 训练时验证集 loss 很低,接入优化器后调度效果却奇差——光伏预测曲线看起来平滑合理,可实际执行时老是偏差很大。把训练时的验证集画出来看,预测值和真实值几乎贴合,一换到线上数据就崩。
原因:这是典型的时间序列数据泄漏。代码里用train_test_split(random_state=42)随机切分,把未来时刻的数据混进了训练集,模型在训练时已经“见过”了测试时段的光伏读数。时序模型和普通回归模型不同,样本之间有强自相关,随机切分会把时间顺序打乱,让模型学到跨时间的捷径而不是真正的映射关系。
解决:时序预测的样本必须按时间顺序切分,验证集只能取最后连续的一段,不能随机抽样。滑窗生成样本时也要注意:训练样本的窗口不能跨越验证集起始时刻。业内常用做法是用最后 10%~15% 的历史数据做验证,前面全部用于训练;如果要调超参就再做一次时序 K 折,但每折的验证集都必须严格在训练集时间范围之后,不能反过来。
split = int(len(X) * 0.85) X_train, X_val = X[:split], X[split:] y_train, y_val = y[:split], y[split:]这个坑几乎是所有“神经网络 + 优化”项目里的头号翻车点,因为它的隐蔽性极强:训练指标漂亮,可视化也漂亮,直到调度系统上线才暴露问题。接入优化器之前,花十分钟画一张“验证集真实值 vs 预测值”的时间序列图,比任何评估指标都有用。
6. 帕累托前沿怎么验证:HV 指标、区间预测与一个进阶方向
多目标优化跑完,不能盯着三张目标值的收敛曲线就说“算法有效”。我习惯做两步验证:第一步看帕累托前沿本身,把成本、碳排放、失负荷率两两组合画散点图,确认前沿是一条平滑、覆盖均匀的曲线而不是几个孤立点;第二步算超体积指标,用参考点把目标空间划分出体积,HV 越大说明解集在目标空间里覆盖得越好。
def compute_hypervolume(points, ref_point): # 简化教学版:网格采样法近似,正式环境用 WFG 标准实现 sampled = 0 hit = 0 for _ in range(10000): p = np.random.uniform(0, ref_point, size=ref_point.shape) for pt in points: if np.all(p <= pt): hit += 1 break return hit / 10000 * np.prod(ref_point)这里的参考点要取每个目标在所有代里见过的最大值再放大一点,不能取理论最劣值——理论最劣值会把 HV 计算变成一个对目标范围极其敏感的数值。网格采样法只适合教学和快速验证,正式的对比实验建议用标准库的 HV 实现。HV 值在实验里有两个用途:一是判断单次运行是否收敛,二是做多算法对比时对每个算法跑 10 次独立实验取均值和方差,而不是拿一次最好结果说话。
进阶方向我把最近在尝试的做法一并说了:点预测的误差在优化里是被忽略的,而光伏预测误差有很强的时段性——清晨和傍晚误差大,中午晴天误差小。用 LSTM 的分位数回归输出 5%、50%、95% 三条曲线,把区间带传进优化器,约束条件改成“在最差预测场景下也不能失负荷”,这样得到的调度计划天然带鲁棒性。代价是约束变严,成本会上升,但这才是工程上真正能落地到 EMS 里的方案。
前两年我做这套系统时坚持用均值预测接入优化器,前沿图非常漂亮,结果夏天午后一场阵雨让光伏出力掉了一半,调度计划当场翻车。从那以后,我所有预测模块都强制要求输出不确定度,哪怕只是简单计算历史预测残差的标准差。技术方案再先进,最终也要给不确定性留一条后路。这个教训分享给你,希望帮到你。
本文还有配套的精品资源,点击获取