风光出力随机波动、EV充放电行为不确定,再加上配电网的潮流约束,这问题摆在一起并不是"把三块内容拼起来"那么简单。我最早在做IEEE33节点算例时,偷懒把风光出力直接取典型日曲线,EV负荷当成固定负荷塞进潮流,结果调度方案在仿真里跑得通,自己心里却清楚:换一组真实风光数据,方案大概率就失真了。后来把场景生成、自适应遗传算法、并网调度模型串成完整流程,用Matlab在IEEE33节点系统上跑通,才算是把这套东西真正捋顺。这篇文章就把我的完整实现思路和代码设计分享出来,适合正在做配电网优化调度方向的毕业设计、课题组横向项目,或者刚接触随机优化想找一套能落地框架的朋友参考。文章不堆公式,重点讲清楚每一步为什么要这么做、代码结构怎么组织、有哪些我一踩再踩的坑。
1. 风光出力与EV负荷的不确定性:这个调度问题难在哪
1.1 一条"典型日曲线"解决不了的问题
很多初接触这个方向的同学会问:风电、光伏出力不是有预测曲线吗?直接拿预测值做优化不行吗?答案是:做确定性优化当然行,但结果在工程上没有意义。
电网调度最基本的硬约束是功率平衡,说白了就是"发多少、用多少,必须对上"。风光出力本质上是随机过程,受风速、光照、云量影响,预测误差随预测时长的增加急速放大。你把风光出力固定成一条曲线,相当于假设未来完全已知,在这种假设下求出的调度方案是"上帝视角最优解",实际执行时一旦风光偏离预测曲线,节点电压就可能越限,支路可能过载,EV充放电计划也会跟着乱。
我在一次复现对比中做过测试:用确定性模型求出的调度方案,直接套到另一组波动稍大的风光场景下重新做潮流校验,结果有3个节点电压越过了1.05 p.u.的上限。这就是审稿人或者导师让你"考虑不确定性"的根本原因——好的调度方案不是在某一条曲线下最优,而是在一大类可能的出力情形下都可行、都够好。
1.2 三个随机源叠加,问题变成了"场景下的期望优化"
IEEE33节点系统里接入风电、光伏和EV后,随机源至少有三个:
- 风电出力:由风速决定,带有明显的季节和昼夜特性,波动幅度大;
- 光伏出力:跟光照强度强相关,夜间出力为零,白天呈钟形曲线,受云层影响会有分钟级波动;
- EV充放电行为:车主什么时间插枪、每天行驶里程多少、是否愿意参与V2G放电,这些都是典型的随机变量。
更要命的是这三个随机源还有时间耦合。晚高峰时段恰恰是EV集中充电的时间,而这时光伏出力恰好为零;夜间风电出力往往偏高,EV如果都能参与V2G,反而可以帮电网"削峰填谷"。
处理这种多随机源耦合问题的标准思路就是场景法:把连续的不确定性空间离散化为一组带概率的典型场景,在每个场景下都做潮流和调度计算,最后以期望值评价方案性能。这样做的好处是:不确定性的传递路径清晰,物理约束可以在每个场景下逐条校验,而且最终得到的调度方案在概率意义上是可行的。
2. 风光场景生成:从历史出力数据到十个典型场景的完整链路
2.1 用核密度估计拟合历史出力分布
场景生成的第一步,是把风电场、光伏电站的历史出力数据拿出来,拟合出各自的概率分布。
这里有个选择:直接用威布尔分布拟合风速、再用风速-功率曲线转换成功率?还是直接拟合功率的历史分布?我在实际项目中选的是后者,用**非参数核密度估计(KDE)**直接对历史功率序列建模。
原因是风电、光伏的功率分布形态在不同季节、不同天气系统下差异很大,可能双峰、可能严重偏态,参数化分布假设很容易失真。KDE的思路很简单:在每个历史样本点附近放一个高斯核,把所有核叠加起来,就得到一条平滑的概率密度曲线。带宽参数控制平滑程度,Matlab里用ksdensity一行就能实现,比手动拟合威布尔分布参数方便得多,精度也更好。
2.2 拉丁超立方采样与相关性还原
分布拟合好之后,要生成大规模的初始场景样本。这里我不用蒙特卡洛随机采样,而用拉丁超立方采样(LHS)。
LHS的核心思想是分层:把每个变量的累计概率区间均匀分成N层,每层只采一次,然后打乱各变量的采样次序组合成样本。相比纯随机采样,LHS用少得多的样本量就能覆盖整个分布空间,尤其是尾部的高出力、低出力极端情形,这对调度问题非常重要——极端场景往往才是约束最容易越限的地方。
采样之后还有一个关键问题——相关性。同一片区域的光资源和风资源往往受同一个天气系统支配,风电出力和光伏出力之间不是独立的。比如多云大风天,光伏出力被云层压得很低,风电出力却很高。如果忽略这个负相关性,生成的场景就不符合物理规律。
处理方法是:先把LHS样本通过分位数函数变换到标准正态空间,加上相关系数矩阵,用Cholesky分解对样本做线性变换,再变换回原始分布空间。Matlab里可以用copularnd配合norminv、normcdf实现,或者手动写分位数变换。这一步看起来只是数学细节,但直接影响后续场景削减出来"像不像真实数据"。
2.3 K-means聚类削减:从上千个场景到十几个典型场景
初始场景生成500到1000个都不为过,但优化调度不可能把每个场景都塞进去算一遍——计算量承受不住。所以要做场景削减。
我推荐的做法是K-means聚类:把形态相似的出力曲线归到同一个簇,簇的质心就是典型场景,簇内样本数占总样本数的比例就是该场景的权重概率。实际操作中,我的参数设置是初始场景800个,聚类个数10到15个,具体取多少要看聚类结果的包络形态是否覆盖高、中、低出力三种典型情形。
聚类做完必须做校验,不能直接拿质心就用。我通常校验两个指标:削减前后场景集合的期望出力偏差和方差偏差。偏差控制在2%以内就认为削减合理。这两个指标在Matlab里几行代码就算完,但很多人忽略这一步,结果削减出来的场景明显失真,后面所有优化结果都跟着失真。
2.4 最容易翻车的地方:时间相关性
场景生成里最容易翻车、也最容易被审稿人抓的,是时间维度上的自相关性。
有的实现是每个时段独立采样,生成的风光出力序列在时间上完全孤立:上一时刻还是晴天正午的光伏高发,下一时刻直接跳到夜间零出力。这显然不符合物理规律——真实的出力曲线是连续的,相邻时段有强相关。
解决思路有两种。第一种是把一整天的24小时出力曲线当成一个多维随机向量,整体采样,相关性矩阵里包含时刻之间的自相关;第二种是引入风速和光照的时序演化模型,比如用一阶马尔可夫链或者AR模型描述相邻时刻的状态转移。我在代码里用的是整体采样方法,因为它和KDE分布拟合、LHS采样是天然衔接的,改动最小、代码最稳定。
3. 并网调度模型怎么建:目标函数、约束边界与V2G折算
3.1 目标函数:网损、购电成本与弃风弃光惩罚
调度模型的目标函数,我日常用的是三层结构,从主到次分别是系统网损、上级电网购电成本、弃风弃光惩罚量。写成数学形式就是:
min F = w1 * sum(网损) + w2 * sum(购电功率 * 分时电价) + w3 * sum(弃风功率 + 弃光功率)其中w1、w2、w3是权重系数,根据项目侧重点调节。网损由潮流计算结果给出,就是IEEE33节点上所有支路的有功损耗之和;购电功率就是根节点(变电站)注入配电网的功率;弃风弃光惩罚项的存在,是为了防止优化结果为了省网损而强行压低风光出力——那和新能源消纳的目标就背道而驰了。
如果你做的是EV有序充电方向,可以再加一个EV用户充电费用最小化的目标,或者把用户充电费用满意度约束嵌进去,让调度方案既对电网友好、又对车主友好。不过首次跑通的话,我建议目标函数先保持精简,等整体流程验证没问题再加层次。
3.2 约束条件:电压、支路容量与功率平衡
约束条件分为四类,每一类在代码里对应不同的处理方式:
| 约束类型 | 数学表达 | 代码处理方式 |
|---|---|---|
| 潮流平衡约束 | 各节点有功/无功注入等于流出 | 目标函数内部调用潮流计算隐式满足 |
| 节点电压约束 | 0.95 ≤ Ui ≤ 1.05 p.u. | 越限则施加罚函数 |
| 支路容量约束 | Sij ≤ Smax | 越限则施加罚函数 |
| 可控设备出力约束 | 出力上下限各节点不同 | GA初始化时限定在边界内生成 |
节点电压约束和支路容量约束是调度方案可行性的核心。实际配电网中,EV集中充电会造成末端节点电压跌落,光伏大发可能造成电压抬升甚至逆潮流——这两种情况都会直接触发电压越限。罚函数的具体逻辑在后面第4节展开。
3.3 V2G场景下EV怎么折算成节点注入功率
EV在模型里的角色,本质上是可移动的分布式储能。每辆EV有四个关键参数:电池容量、当前SOC、充电功率上限、是否允许V2G放电。
折算方式是这样的:一个EV充电站接入配电网某个节点,该节点的等效负荷等于站内所有EV充电功率之和减去放电功率之和:
P_node_ev = Σ P_charge - Σ P_dischargeP_charge是正负荷,P_discharge是负负荷。这里有个物理约束必须写进模型:一天内EV从电网获取的总电量必须满足车主的出行需求。我用如下不等式表达:
Σ(充电电量) - Σ(放电电量) ≥ 日行驶耗电量 + 末端SOC目标 - 起始SOC电量也就是说,你可以让EV在晚高峰放电帮电网削峰,但全天下来车主充进去的电量减去放出去的电量,至少要覆盖当天跑掉的里程和到达目的地时的最低电量要求。这个约束不满足,调度方案对用户就是不可行的——你的方案确实降低了网损,但车主半路没电了,这方案没人会接受。
4. 自适应遗传算法求解:标准GA的早熟困境与改进逻辑
4.1 为什么标准遗传算法在这个问题上容易早熟
IEEE33节点优化调度是一个高维、多峰、非线性的优化问题:决策变量包括多个EV充电站的24小时充放电功率、可控DG出力等,维度动辄几十到上百。目标函数没有解析梯度,而且存在大量局部极值。
标准遗传算法(SGA)用固定的交叉概率Pc和变异概率Pm。固定参数的问题在于:迭代初期希望Pc和Pm大一些,保持种群多样性、充分搜索解空间;迭代后期希望Pc和Pm小一些,让好个体稳定收敛。但固定参数两头不讨好——Pc、Pm设得大,后期收敛慢甚至振荡;设得小,前期搜索能力不足,种群迅速趋同,陷入局部最优出不来。
我实测过一组对比:标准GA在种群规模60、迭代200代的设置下,大约第65代就收敛了,但这个收敛值比自适应GA最终找到的最优解差了约7%。这不是偶然——多峰问题在缺乏多样性维持机制时,收敛到局部极值的概率非常高。
4.2 自适应机制的核心:让概率跟着适应度走
自适应遗传算法(AGA)解决这个问题的思路非常巧妙:交叉概率Pc和变异概率Pm不再固定,而是根据当前种群适应度的分布动态调整。
经典的自适应机制用四个常量和一个规则描述。设fmax为当代种群最大适应度,favg为平均适应度,f'为要交叉的两个个体中较大的适应度值,f为要变异的个体适应度,则:
- 当 f' ≥ favg 时:交叉概率 = k1 × (fmax - f') / (fmax - favg);否则取较大常数 k2;
- 当 f ≥ favg 时:变异概率 = k3 × (fmax - f) / (fmax - favg);否则取较大常数 k4。
这个公式的含义很直白:适应度高于平均值的"优秀个体",给较小的Pc和Pm,尽量保护它们不被破坏;适应度低于平均值的"落后个体",给较大的Pc和Pm,促使它们变化、寻找新方向。种群整体越接近,fmax和favg越接近,(fmax - f') 和 (fmax - favg) 的比值越大,Pc和Pm自动提高,相当于在快要早熟时强制"搅一搅",把多样性拉回来。
k1到k4的取值,我常用的是一组通用参数:k1=1.0、k2=1.0、k3=0.5、k4=0.5。但要注意,这组参数不是万能,具体问题可能需要微调。调参的经验是:k3、k4决定探索强度,偏大容易振荡,偏小容易早熟,可以先用默认值跑一遍看收敛曲线再决定方向。
4.3 实数编码:把调度决策变量映射到染色体
编码方式是求解效率和可解释性的关键。我使用的是实数编码,一条染色体就是一个决策向量。比如:
染色体 = [EV1_24h充放电功率(24维), EV2_24h充放电功率(24维), 可控DG出力(若干维)]编码顺序一旦确定,程序里解包时按固定索引切片即可。实数编码相比二进制编码的优势在于:不用编解码、精度无损、和决策变量物理意义直接对应、交叉变异算子实现简单。对数值优化问题,实数编码是更自然的选择。
4.4 罚函数:越限个体怎么"拉回来"
对于电压越限、潮流不收敛的个体,我用罚函数处理,而不是直接淘汰。罚函数的形式是:
适应度 = 目标函数值 + λ × (电压越限量的平方和 + 支路过载量的平方和)罚函数设计的要点是罚的力度要适中。罚太轻,可行解和不可行解的区分度不够,种群里混着大量不可行解,拖慢收敛;罚太重,少数可行解过早垄断种群,多样性垮掉。我用的经验法则是:让不可行解的适应度比当前可行解中最差的还要差,但不至于让不可行解在种群中瞬间清零——留一点"过渡个体"对搜索是有帮助的,因为很多不可行解其实离可行域很近。
5. IEEE33节点测试系统:参数清单、Matlab代码结构与实测效果
5.1 IEEE33节点的关键参数:抄作业版
IEEE33节点系统是配电网优化领域最常用的标准辐射状算例,基本参数需要记牢:
| 参数项 | 数值 |
|---|---|
| 基准电压 | 12.66 kV |
| 节点数量 | 33个 |
| 支路数量 | 32条 |
| 系统总有功负荷 | 3715 kW |
| 系统总无功负荷 | 2300 kvar |
| 根节点类型 | 平衡节点(变电站出口) |
我通常的接入方案是:风电接在节点17(末端重负荷区),光伏接在节点32(馈线最末端),EV充电站分布在节点18和25。这样布置的考虑是:末端节点电压最脆弱,正好考察风光接入和EV充放电对电压分布的影响。
5.2 Matlab代码结构:五个文件各司其职
代码我按功能拆成五个模块文件,主程序负责串联:
| 文件 | 功能 | 关键输出 |
|---|---|---|
init_parameters.m | 系统参数、算法参数集中设置 | 结构体params |
scenario_generation.m | KDE拟合、LHS采样、聚类削减 | 典型场景数组+概率权重 |
powerflow.m | 前推回代潮流计算 | 节点电压、支路功率、网损 |
objective_function.m | 目标函数+罚函数 | 个体适应度 |
adaptive_ga.m | 自适应GA主循环 | 最优解、收敛曲线 |
主程序流程是:初始化参数 → 生成场景 → 初始化种群 → 进入GA循环(选择、自适应交叉、自适应变异、计算适应度)→ 输出结果。
这样的模块化结构,调试时能单独验证每一个环节。我最常单独调试的就是scenario_generation.m和powerflow.m——这两个模块如果出错,后面GA调得再好也白搭。
5.3 为什么用前推回代潮流而不是牛顿拉夫逊
IEEE33节点是纯辐射状网络,对于这种拓扑,前推回代法(backward/forward sweep)比牛顿拉夫逊法更合适。思路简单得惊人:
- 假设所有节点电压初值为1.0 p.u.;
- 从末端节点向根节点"前推":根据节点负荷和注入功率,逐段计算支路电流和功率;
- 从根节点向末端"回代":根据支路电流和线路阻抗,逐段更新节点电压;
- 重复迭代,直到相邻两次迭代的节点电压最大偏差小于设定阈值(我用1e-6 p.u.)。
这个方法在IEEE33节点上几毫秒就能收敛,实现代码量只有牛顿拉夫逊法的三分之一不到,而且没有矩阵求逆,数值稳定性好。如果用Matpower,当然也能算,但对这个特定问题,前推回代法无论是速度还是可定制性都更合适。
5.4 实测中踩过的三个坑
第一个坑:随机数种子不固定。场景生成、GA初始种群都涉及随机操作,如果不设置随机种子,每次运行结果都不同,后面做结果分析根本没法复现。我建议在init_parameters.m里统一调用rng(2024)这类固定种子,一次实验、多次可复现。
第二个坑:初始种群不能纯随机生成。纯随机的染色体大量落在约束边界之外,罚函数虽然能把它们拉回来,但没有梯度方向的信息,收敛速度极慢。正确做法是:在决策变量物理上下界范围内做均匀随机初始化,确保起点分布在可行域附近。
第三个坑:GA交叉变异后,决策变量可能超出物理边界。比如充电功率变成负的、或者超过了充电桩上限。这需要在交叉变异之后,加一个边界钳制函数,把超限基因位钳制到边界值。这个细节不处理,适应度计算会得到一堆无法解释的异常值。
6. 结果分析怎么做:收敛曲线、场景包络与参数灵敏度
6.1 收敛曲线怎么看
程序跑完之后,第一件事不是看最优值,而是看每代最优目标值的收敛曲线。
正常的自适应GA收敛曲线应该是三段式:前期快速下降、中期小幅振荡后缓慢下降、后期趋于平直。如果后期还在大幅振荡,说明变异概率上限调得偏大;如果前期就压平了,说明初始种群质量不够或者种群规模太小。
我曾经跑出一版前期直接压平的收敛曲线,排查了一下,根因是罚函数设太重,种群前几代就被迫向一个局部最优解集中,多样性彻底丧失。把罚函数权重调低后,曲线才恢复正常的"阶梯式下降"形态。
6.2 场景包络怎么评估
场景生成模块跑完,把10个典型场景的风光出力曲线叠画在一张图上,观察包络形态:
- 如果各场景曲线拥挤在一起、形态差异很小,说明聚类数偏少或者削减过度;
- 如果场景曲线之间差异剧烈、出现明显不合理跳跃,说明初始采样出了问题,多半是时间相关性没处理好;
- 合理的包络应该覆盖高出力、中出力、低出力三种典型情形,同时每条曲线在时间维度上保持连续。
我还习惯检查一个细节:典型场景的概率权重之和是否等于1,最高权重场景占比是否超过50%。权重过于集中说明聚类结果失去了代表性,需要增加聚类数。
6.3 参数灵敏度测试决定你结论的可信度
最后建议做一组简单的参数扫描测试:种群规模从40取到80,迭代代数从100取到300,场景数从5取到15,各跑5次取平均最优值,画一张热力图或折线图。
这个步骤有几个价值:第一,确定你的代码在哪个参数区间内表现稳定,为论文的实验设置提供依据;第二,证明你的算法不是"只在某组参数下偶然有效";第三,自己心里有数——在什么参数条件下结果开始劣化,以便后续横向对比时不会被质疑。
以我个人经验,这个测试值得做。我最初跑出来的结果总是比其他文献里的目标值差一截,查了半天代码逻辑都没问题,最后发现是场景数设了5个、聚类太少导致置信度不足。把场景数调到12之后,目标值才和文献对得上。
7. 最后再分享两个小经验
整个流程跑通之后,我对这类"场景生成+智能优化"框架有了几点体会,供你参考。
第一,别把时间过多花在调遗传算法参数上。我更建议把精力放在场景削减后的校验和潮流收敛判据上——这两个地方出的问题,对结果的影响远大于GA参数的轻微差异。我在项目里见过太多次"辛苦调了两周参数,最后发现是场景生成环节的时间相关性问题导致结果不合理"的情况。
第二,代码保留一套可复现的测试档。固定随机种子之后,把测试结果存成一个MAT文件,包含典型场景、最优调度方案、节点电压分布。后续任何一次修改代码,都要先和这份基准结果对比,确认变化是修改带来的还是随机性带来的。这套工作习惯,极大节省了我在多轮实验对比上的时间。