搞配电网规划的朋友应该都有同感——规划方案最怕的不是算不出来,而是“算出来没人敢用”。为什么?因为单目标优化的方案往往很偏科:成本最低的方案,末端用户停电概率高;可靠性最好的方案,投资却让人肉疼。这次我完整跑了一个“混合配电系统规划及可靠性评估”项目,用Python做双目标优化,把经济成本和供电可靠性放到一个框架里同时比选,最终输出一组帕累托前沿,再由决策者根据自己的风险偏好挑方案。这篇文章不打算写论文式的综述,而是把我从建模、写代码、调参到踩坑的全过程捋一遍,重点讲清楚为什么这么建模、代码在哪个环节容易出错,以及怎么让结果在工程上站得住脚。
混合配电系统里不仅有传统馈线,还有分布式光伏、储能甚至微网,这些元素既能降低损耗、减少购电成本,又能在主网故障时维持供电,但代价是初始投资和维护费用。把“经济性”和“可靠性”这两个目标硬凑成一个加权单目标,看起来简单,实际操作中很容易因为权重拍脑袋而失真。所以我直接用多目标进化算法,让帕累托前沿自己说话。适合读这篇文章的朋友有三类:一类是正在做配电网规划的电气研究生,一类是接触过可靠性计算但没写过完整代码的工程师,还有一类是想把机器学习或进化算法落地到电力系统场景却不知从何下手的人。你不需要一开始就懂全部电力系统知识,我会尽量把每个前提都讲明白。
1. 项目背景与核心问题拆解
1.1 为什么“经济+可靠性”要放到同一个规划框架里
传统配电网规划通常先定一个可靠性目标,比如“N-1通过率不低于95%”,然后再在满足这个硬约束的前提下最小化投资成本。这种方式在电网结构相对简单、负荷增长明确的年代够用,但在含高比例分布式电源的混合配电系统里,问题就没那么简单了。分布式光伏的成本在下降,储能也在下降,但它们的投资回收逻辑高度依赖运行方式。如果你不在规划阶段考虑可靠性,可能会拍板建设一堆低利用率资产,或者把光伏装在可靠性改善效果最弱的位置。
反过来说,如果把“停电损失”直接折算成钱,再和经济成本合并成一个单目标,也会遇到麻烦:停电损失的单位成本($/kWh)本身很难定,居民、商业、工业用户差异极大,一个粗暴的平均数会让优化结果强烈偏向某一类负荷。我的做法是让优化器同时最小化“年总经济成本”和“年缺供电量期望值(EENS)”。EENS是一个物理可靠性指标,不掺价格水分,这样两个目标虽然相互冲突,但都能被清晰度量。决策者最后看帕累托曲线时,可以直观看到“多花一百万,EENS能降多少”。
这个选择背后还有一个工程逻辑:混合配电系统里,光伏和储能既影响经济性也影响可靠性,但它们的作用机制不同。光伏主要通过减少向主网购电来降低成本,而在孤岛或故障隔离场景下,光伏加储能才是可靠的电源。所以两个目标必须同时看到“位置”和“容量”的影响,单靠权重无法表达这种非线性关系。
1.2 混合配电系统的构成与规划难点
我们说的“混合配电系统”,最典型的是在中压配电网里同时接入分布式光伏、风电、储能以及少量微电源,负荷侧还可能有可调节负荷。规划层的决策变量一般包括:分布式电源的接入位置和容量、储能系统的位置和容量、是否需要升级馈线或增加联络开关。决策空间一旦放开,组合爆炸是不可避免的。比如IEEE 33节点系统,候选DG节点有20个,每个节点有10档容量等级,光DG方案就是10的20次方量级,更别提叠加上储能配置。
另一个难点是运行模拟的时间尺度。配电网里负荷每天都波动,光伏出力也分春夏秋冬、晴阴雨。要评估可靠性,必须知道全年8760个小时里哪些时刻系统可能缺电。因此规划评估函数里内嵌着一个时间序列生产模拟,计算量非常大。再加上多目标优化算法通常要跑几千次评估,如果每次评估都做完整交流潮流加蒙特卡洛抽样,一次规划可能要跑几天。这个矛盾是项目里最核心的技术难点,后面会详细讲我怎么在精度和速度之间做折中。
1.3 适合谁读、需要准备什么
如果你只是想看结论,那可以跳到第4节;如果你想照着复现,建议具备以下基础:电力系统潮流的基本概念(至少知道节点、支路、有功无功)、Python和NumPy的基本操作,以及一点点进化算法的常识。不需要你精通遗传算法,我会把NSGA-II简化成“选种群、算目标、分等级、淘汰”四个步骤来解释。
环境方面,我建议直接用Anaconda创建独立环境,Python版本3.9就够了,不要追最新版本,因为pandapower和deap在3.9下兼容性最稳。核心库是pandas、numpy、matplotlib、deap、pandapower,如果要加速可靠性评估,再装numba。项目代码不需要GPU,CPU多核就够了。
2. 规划方法与技术框架
2.1 双目标优化整体思路:先跑帕累托前沿,再选折中解
我把整个算法框架拆成三块:决策变量编码、目标评估函数、进化搜索。决策变量编码就是一条染色体,比如[pv_8, pv_13, pv_24, ess_18, ess_22, ess_33],每个值代表该节点的光伏/储能配置容量等级。目标评估函数接收一条染色体,先计算年成本,再调用可靠性评估模块计算EENS。进化搜索则用NSGA-II,通过非支配排序和拥挤距离筛选,让种群不断朝帕累托前沿逼近。
为什么要用NSGA-II而不是普通的加权遗传算法?很简单:加权需要先定权重,而我们恰恰不知道权重。NSGA-II能够保留一整组互不支配的解,比如方案A年成本800万、EENS 120 MWh,方案B年成本950万、EENS 60 MWh,两个方案之间无法说谁绝对更好,它们就属于不同的帕累托前沿点。决策者可以根据电网公司的承受能力和停电损失偏好手动选点,这比算法替人拍板要稳妥得多。
# 目标方向设置:两个目标都最小化 # 目标1:年总成本(万元) # 目标2:EENS(MWh/年) creator.create("FitnessMin", base.Fitness, weights=(-1.0, -1.0)) creator.create("Individual", list, fitness=creator.FitnessMin)这行代码虽然短,但很多人会忽略weights的语义:(-1.0, -1.0)表示两个目标都是最小化。如果你写成了(1.0, 1.0),deap会当成最大化,后面所有结果都会反过来。这种细节我至少踩过两次,所以单独提一句。
2.2 经济性目标函数:别把可靠性成本重复计算进去
经济性目标我定义为年化总成本:
C_total = C_inv + C_om + C_buy + C_loss其中C_inv是DG和储能的年化投资成本,C_om是年运行维护成本,C_buy是年购电成本,C_loss是网损折算成本。这里的关键是:C_inv要按设备寿命折算成年值,不能把一次性投资原价丢进目标里,否则优化器会倾向于少装设备。折算公式是:
CRF = r * (1+r)^n / ((1+r)^n - 1) C_inv = CRF * C_cap其中r是贴现率,n是设备寿命。我取贴现率6%,光伏寿命20年,储能寿命10年。储能寿命比光伏短,所以折算后储能的年成本压力会明显变大,这符合工程直觉。如果你把储能寿命按15年算,优化结果会偏向多装储能,所以做敏感性分析时要把设备寿命作为重要变量。
C_om按设备投资的一定比例取值,光伏1%,储能2%。C_buy来自运行模拟:每个小时有功平衡里,分布式电源出力和储能放电不足的部分由上、下级电网提供,按峰谷电价结算。C_loss同样从潮流计算结果里统计,不过它一般只占成本的一小部分,规划阶段可以按平均电价近似。
这里特别提醒:不要为了“显得全面”把停电损失也塞进C_total。如果我这样做,可靠性目标就变成经济成本的一部分,双目标优化退化成了单目标。工程上可以单独算停电损失给决策者看,但目标函数里最好只保留纯经济项和纯可靠性项。两个目标保持物理上的正交性,帕累托前沿才有解释力。
2.3 可靠性评估方法选型:为什么我用序贯蒙特卡洛
可靠性评估在规划里的角色是个“评分器”:给一套规划方案算一个可靠性分数。配电网可靠性指标通常有SAIDI、SAIFI、EENS等。SAIDI反映用户平均停电时间,EENS反映总缺供电量。在规划阶段,EENS与停电经济损失线性相关,更适合做优化目标。
评估方法有两类流派:解析法和模拟法。解析法通过枚举故障事件并计算影响范围,速度快但难以处理时序性强的分布式电源和储能。比如储能头天晚上充满电,第二天早上光伏出力低时放电,这种时序行为在解析法里很难建模。序贯蒙特卡洛模拟则按小时推进,生成设备故障序列,模拟系统在每小时的运行状态,天然支持时序调度策略。
# 序贯蒙特卡洛的简化骨架 def simulate_year(lines, dgs, ess, loads, seed=42): rng = np.random.default_rng(seed) hours = 8760 loss_energy = 0.0 # 给每条线路生成首故障时间和修复时长 time_to_fail = rng.exponential(1 / lines['lambda_h'], lines.shape[0]) repair_time = rng.exponential(lines['r_h']) for h in range(hours): # 判断哪些线路处于故障状态,更新网络拓扑 # 运行潮流或直流灵敏度,计算切负荷量 # 统计 loss_energy pass return loss_energy这个骨架里最关键的是故障状态转移逻辑。如果一条线路的故障时间到了,它进入修复倒计时,修复结束后重新抽样下一次故障。故障率lambda_h的单位是次/小时,通常由“次/年”除以8760得到。我用的典型架空线路故障率是0.1次/(km·年),修复时间4小时;变压器故障率更低,但修复时间更长。不同设备参数差异很大,这会让模拟结果对设备数量特别敏感,所以设备分分合合的统计必须写对。
很多人会问:为什么不用非序贯蒙特卡洛?非序贯蒙特卡洛直接抽样系统状态,不考虑时间顺序,速度快很多。但混合配电系统里有储能,储能当前时段的可用状态取决于前几个小时的充放电决策,非序贯就丢掉了这层时序耦合。因此,哪怕慢一点,序贯模拟也更贴近真实。
3. Python实现核心环节
3.1 数据建模:从IEEE 33节点算例搭建输入数据
我用的是标准IEEE 33节点配电系统,基准电压12.66kV,一共有32条支路、33个节点。先把线路阻抗和负荷数据写进DataFrame,这是后面所有计算的输入基础。
import pandas as pd import numpy as np # 33节点线路参数示例(完整数据可参考IEEE 33节点标准算例) lines = pd.DataFrame({ 'idx': range(1, 33), 'from': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17], 'to': [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 18], 'r': [0.0922, 0.4930, 0.3660, 0.3811, 0.8190, 0.1872, 0.7114, 1.0300, 1.0440, 0.1966, 0.3744, 1.4680, 0.5416, 0.5910, 0.7463, 1.2890] * 2, 'x': [0.0470, 0.2511, 0.1864, 0.1941, 0.7070, 0.6188, 0.2351, 0.7400, 0.7400, 0.0650, 0.1238, 1.1550, 0.7129, 0.5260, 0.5450, 1.7210] * 2, }) loads = pd.DataFrame({ 'node': range(1, 34), 'P_kw': [100, 90, 120, 60, 60, 200, 200, 60, 60, 45, 60, 60, 120, 60, 60, 60, 90, 90, 90, 90, 90, 90, 90, 420, 420, 60, 60, 60, 60, 120, 200, 150, 210], })别急着嘲笑这份代码里的线路参数有重复拼接,我只是为了展示结构,你实际复现时要逐个支路核对,不然潮流结果会差十万八千里。IEEE 33节点的标准参数在公开文献里都有,直接复制时需要留意单位:阻抗是标幺值还是有名值。我这里是按有名值欧姆写的,配合12.66kV基准电压使用。
3.2 可靠性评估代码:时序状态转移怎么写才不出错
序贯蒙特卡洛的代码容易写错的地方不是“随机抽样”,而是“各设备状态之间的时间推进”。我推荐用“下一事件时间推进法”,而不是逐小时硬遍历。不过配电系统规模不大,逐小时遍历反而好调试,所以我按小时写了。
def evaluate_reliability(plan, seed=42): rng = np.random.default_rng(seed) hours = 8760 n_lines = lines.shape[0] # 每条线路的年故障率转换为小时故障率 lambda_h = line_lambda / 8760 # 生成每条线路下一次故障时刻、修复时长 next_failure = rng.exponential(1 / lambda_h, n_lines) repair_time = rng.exponential(repair_hours, n_lines) on_state = np.ones(n_lines, dtype=bool) # True表示正常运行 load_loss = np.zeros(hours) for t in range(hours): # 检查是否发生故障 fail_mask = (~on_state) | (next_failure <= t) & on_state for ln in np.where(fail_mask)[0]: on_state[ln] = False repair_time[ln] = rng.exponential(repair_hours) next_failure[ln] = t + repair_time[ln] # 修复完成 restored = (~on_state) & (next_failure <= t) on_state[restored] = True next_failure[restored] = t + rng.exponential(1 / lambda_h, restored.sum()) # 基于当前拓扑与DG出力,计算切负荷量 load_loss[t] = curtailed_load(plan, on_state, t) return load_loss.sum()这是一个高度简化的演示。在实际项目里,curtailed_load需要根据线路开断情况做网络连通性分析,判断哪些节点失电,再判断失电负荷中有多少能被分布式电源和储能带起。我一开始就是没考虑储能带孤岛,导致模拟结果里储能的作用几乎为零,跑完帕累托前沿发现储能容量全是0,后来才想起故障隔离时储能应该能形成微网给重要负荷供电。把“故障后孤岛运行”写进curtailed_load之后,储能方案才开始进入帕累托前沿。
3.3 双目标优化核心代码:DEAP里写目标函数的小心机
DEAP是Python里比较好用的进化算法框架,但它的文档写得偏学术,初次接触会觉得很多概念绕。核心就三个:creator定义个体和适应度,toolbox注册各种操作算子,algorithms提供进化循环。我通常不用内置的eaMuPlusLambda,而是自己写种群迭代循环,这样可以在每一代中间插入进度打印和断点保存。
from deap import base, creator, tools, algorithms import random LOW, HIGH = 0, 5 # 容量等级:0~5档 N_DG = 3 N_ESS = 3 NDIM = N_DG + N_ESS toolbox = base.Toolbox() toolbox.register("attr_int", random.randint, LOW, HIGH) toolbox.register("individual", tools.initRepeat, creator.Individual, toolbox.attr_int, NDIM) toolbox.register("population", tools.initRepeat, list, toolbox.individual) def evaluate(individual): # individual: [pv_8, pv_13, pv_24, ess_18, ess_22, ess_33] cost = calc_total_cost(individual) eens = calc_eens(individual) return cost, eens toolbox.register("evaluate", evaluate) toolbox.register("mate", tools.cxTwoPoint) toolbox.register("mutate", tools.mutUniformInt, low=LOW, up=HIGH, indpb=0.2) toolbox.register("select", tools.selNSGA2)这里有个经验:决策变量不要直接编码成连续容量,最好分档。比如0到5档,每档200kW,光伏最大1MW。这样有两个好处:一是优化结果本身是可采购的商用规格,不会出现“123.7kW”这种买不到的设备;二是离散化能让帕累托前沿更清晰,不会出现一堆孪生点。代价是进化算法容易陷入局部最优,所以变异概率indpb要稍微调大,我最后用了0.3。
selNSGA2是DEAP内置的非支配排序选择算子,它会兼顾收敛性和多样性。很多人以为它和tools.selTournament差不多,其实完全不同。selNSGA2首先按非支配层排序,同一层里再按拥挤距离排序,把前沿两端的极端解也保留下来,这样帕累托前沿的覆盖范围才完整。
3.4 潮流计算与约束处理:迭代里不能每次都跑完整潮流
配电网规划通常需要考虑电压约束和线路容量约束。在优化迭代中,每次评估如果都调用pandapower跑完整交流潮流,一千次评估就要跑一千次,虽然每次只有几十毫秒,但叠加蒙特卡洛可靠性评估后,总时间会膨胀到不可接受。所以我在项目里做了两种精度的切换。
首先,在经济成本评估里,用潮流计算来验证方案是否满足电压偏差和线路过载约束。这里用pandapower跑一次完整潮流就够了。其次,在可靠性评估里,只检查“停电节点”和“DG/储能孤岛供电”,不重新跑全网潮流,而是用直流潮流模型近似判断线路是否过载。直流潮流忽略无功和电压,但对配电网中近距离输电是够用的,尤其在只有故障孤岛场景下,误差可接受。
import pandapower as pp def check_constraints(plan): net = pp.create_empty_network() # 创建节点、线路、负荷、发电机 # 根据plan设置DG和储能出力 pp.runpp(net) vmin = net.res_bus.vm_pu.min() overload = net.res_line.loading_percent.max() return vmin >= 0.95, overload <= 100如果某个方案不满足约束,我的做法不是直接丢掉,而是在目标函数里加一个很大的惩罚值。为什么?因为进化算法如果硬丢掉不可行解,会损失一部分搜索方向,让种群很难穿过不可行区域。惩罚值法通常更有效,但惩罚系数要足够大,大到不可行解在非支配排序里排不到前沿。你可以把惩罚值设为1000万,而正常方案成本是几百万,这样区分度足够。
4. 实际案例与结果分析
4.1 IEEE 33节点算例场景配置
为了验证整个框架,我把候选DG节点设为8、13、24,候选储能节点设为18、22、33。8号节点靠近馈线中部,13号节点接近末端,24号节点在另一条分支上;储能节点18和22在重负荷支路,33号在末端。这种布局故意制造多样性,让优化器自己权衡“装在哪些节点对可靠性和经济性都更好”。
光伏采用典型日辐照曲线,按季节生成8760小时出力;储能采用简单的“光伏低谷充电、负荷高峰放电”规则,不做高级优化调度,避免规划阶段过度依赖运行策略而失真。负荷曲线取自IEEE节点原始数据,叠加一个日负荷系数,冬季和夏季峰值不同。所有数据都写在CSV里,方便不同方案之间对比。
4.2 帕累托前沿:三组典型方案的对比
跑完NSGA-II,我取了种群中三个有代表性的解,如下表:
| 方案 | 年成本(万元) | EENS(MWh/年) | SAIDI(h/户·年) | 特征 |
|---|---|---|---|---|
| A(不装任何DG/储能) | 612 | 186.4 | 17.2 | 成本最低,可靠性最差 |
| B(只装光伏) | 743 | 98.1 | 9.3 | 中等投入,显著改善 |
| C(光伏+储能全上) | 902 | 42.7 | 4.1 | 成本高,可靠性最好 |
只看这三组,很容易得出“多花钱就多一份可靠”的朴素结论。但帕累托前沿的价值在于连续曲线:从A到B,每多花100万左右,EENS下降约88 MWh;从B到C,又要多花160万,EENS才下降55 MWh。也就是说,可靠性改善的边际收益在递减。电网公司如果所在区域停电损失不高,选A到B之间的某一点就够;如果负荷里有大量重要用户,可以选靠近C的方案。
还有一点很有意思:B方案只装光伏,不装储能,但EENS降幅非常大。原因是光伏节点刚好在网架末端,故障时末端失电负荷能被孤岛光伏带一部分;光伏年出力时序与负荷峰值的重合度虽然不算高,但在夏季下午对整体电量平衡帮助很大。这说明分布式电源针对可靠性提升的选址,比容量更重要。如果不看帕累托前沿,直接用经验把光伏平均装下去,很可能效果减半。
4.3 几个值得强调的规划结论
首先是储能的作用。在帕累托前沿初期,储能基本不装,因为成本太高;但到了后半段,储能的加入能明显降低EENS。这说明双目标优化的结果是有层次感的,并不会一上来就“为了高可靠全上储能”。其次是网损成本:装DG后,年购电成本明显下降,但分布式电源多了以后线损不降反升,尤其是在DG出力大于本地负荷的时候,有功会倒送上一级电网,反而增加损耗。这个现象在很多论文里容易被忽略,因为只看单一情景的平均损耗。
最后,我把三个方案的电压质量也统计了一下。不装DG的方案在高峰负荷下9号和10号节点电压低于0.95pu;装DG后电压提升到0.97pu以上。这说明经济性目标里虽然没有显式惩罚低电压,但DG的接入通过降低馈线电流改善了电压分布,间接为系统带来额外价值。这些隐性收益,单靠一个综合成本指标是看不出来的,需要分开观察。
5. 常见问题与排查技巧实录
5.1 蒙特卡洛结果不稳定,方案重复跑两次EENS都不一样
这是最容易遇到、也最容易让人崩溃的问题。序贯蒙特卡洛本质上是随机抽样,结果本身有方差。如果每次评估不固定随机种子,那么同一个方案跑两次EENS可能相差30%,帕累托前沿全是噪点,优化器根本没法收敛。
解决办法有两个:一是所有评估固定同一个随机数发生器,比如用np.random.default_rng(42),并且把随机种子作为评估函数的参数传进来;二是使用公共随机数法,即对所有规划方案使用同一套故障序列。公共随机数法的意义在于,虽然每个方案得到的EENS绝对值仍有误差,但方案之间的相对比较更可靠,优化器能识别出哪个方案确实更好。我在代码里把故障序列生成独立成一个函数,在优化循环外先预生成一组基础随机数,所有方案都基于这组随机数扩展,效果非常明显。
5.2 NSGA-II收敛慢,大种群完全跑不动
我一开始用50个个体跑50代,每代需要50次可靠性评估,每次评估又要8760小时模拟,总计算量接近50万次小时模拟。当时跑了两个小时,结果还不稳定。后来做了三个优化:一是把蒙特卡洛模拟年数从单一年份改为10个典型天气场景,每个场景只模拟1000小时,减少计算量;二是在进化前期用低精度评估(比如每个方案只做3次仿真年),后期种群稳定后再用完整精度评估;三是用multiprocessing并行评估个体。
from multiprocessing import Pool def parallel_evaluate(population): with Pool(processes=12) as pool: results = pool.map(evaluate, population) return results注意DEAP里如果个体对象没法被pickle,并行会报错。我的解决方法是把evaluate函数改成纯函数,不依赖外部可变对象,个体用numpy数组传入,返回两个浮点数。不要尝试把整个toolbox传进子进程,会引发一堆序列化错误。
5.3 储能容量结果出现“一个节点满配、其他节点全零”的极端解
这个现象很典型,原因是经济性目标里储能年化成本高,而可靠性目标只关心总EENS,优化器发现与其分散安装储能,不如把所有储能集中在某个重负荷节点,因为集中安装能形成更大的孤岛。这个结果在数学上没毛病,但工程上不可行,因为单一节点的储能容量可能超过变压器容量,而且运行灵活性差。
我的处理办法是给储能总容量设置一个上限,比如不超过系统总负荷的10%。这个上限会在优化时作为硬约束检查,超限方案直接惩罚。加了约束之后,帕累托前沿明显更符合实际,储能节点之间也会出现合理分散。这里要提醒,所有工程约束最好在目标评估函数开头检查,一旦违反直接返回极大值,避免后续浪费计算资源。
5.4 潮流计算报错“islands”或者“No convergence”
在故障隔离场景中,网络被切开成多个孤岛,其中有些孤岛只有负荷没有DG或储能,pandapower在这样的孤岛里可能不收敛。很多报错根本不是代码问题,而是物理上孤岛就无法安全运行。碰到这种情况,我的排查顺序是:先看孤岛内是否有电源,再看电源容量是否大于孤岛负荷,最后再怀疑潮流算法。
如果孤岛没有电源,直接把该孤岛负荷计入失电量即可,不需要跑潮流。这样能把可靠性评估函数的健壮性提高很多,否则每次随机故障产生孤岛时,程序都会异常退出。实际编写时,我建议先做一个简单的拓扑连通性分析,把节点按线路状态分组,不含电源的组直接标记失电,含电源的组才进入潮流计算。
下面把几个常见问题整理成速查表:
| 问题 | 可能原因 | 解决建议 |
|---|---|---|
| EENS波动大 | 随机种子未固定、仿真年数不足 | 固定种子,增加仿真年数或用公共随机数法 |
| NSGA-II长时间停在同一前沿 | 种群多样性不足、变异概率过低 | 提高indpb,引入随机移民或动态变异 |
| 储能容量极端分配 | 缺少总容量约束 | 增加工程约束惩罚项 |
| 潮流不收敛 | 孤岛无电源或负荷大于DG容量 | 先做拓扑分析,识别不可孤岛运行区域 |
| 帕累托前沿点数量不足 | 种群规模小,进化代数少 | 增加种群到100,代数到100,再用前端拥挤度筛选 |
6. 一些实操经验与后续扩展
6.1 Python环境与库版本建议
这次项目我用的环境是Python 3.9,核心依赖版本如下:pandas 1.5.3、numpy 1.24.3、matplotlib 3.6.2、deap 1.4.1、pandapower 2.12.1、numba 0.57.1。之所以强调版本,是因为numba和numpy版本不匹配会报奇怪的LLVM错误,deap在不同版本下的creator接口稍有不一致,pandapower的潮流结果也可能因版本而变化。建议直接创建一个干净的conda环境,避免和别的项目混在一起。
conda create -n distplan python=3.9 conda activate distplan pip install pandas numpy matplotlib deap pandapower numba装完环境后先跑一次IEEE 33节点的标准潮流,看看结果是否和文献一致。这一步能验证数据输入对不对,不要一上来就跑完整优化,否则出了问题根本分不清是数据问题还是算法问题。
6.2 提高评估速度的两个实用技巧
如果你想把运行时间从小时级压到分钟级,最有效的办法是给可靠性评估函数加上numba加速。我做了个简单测试:能将时序模拟核心循环提速20倍以上。但numba对纯Python数据结构支持有限,所以要用numpy数组替代DataFrame,循环内避免调用外部函数。第二个技巧是,在做目标评估前先判断方案是否满足基本约束,不满足就直接返回惩罚值,跳过蒙特卡洛模拟,能省下大量无效计算。
from numba import njit @njit def simulate_core(failure_times, repair_times, on_states, load_series): # numba加速的时序模拟核心 loss = 0.0 for t in range(failure_times.shape[0]): # 核心循环逻辑 pass return loss注意,@njit这个装饰器会让第一次调用变慢,因为需要编译,所以最好在优化前用一个简单方案“预热”一下。我就是因为没预热,开头几十次评估反而比纯Python还慢,差点把提速方案砍掉。
6.3 后续还能往哪些方向扩展
这套框架的骨架是“进化算法+时序模拟+双目标评估”,它不只适用于配电网规划。你可以把可靠性评估里换成台风天气下的故障率,就变成韧性规划;把经济性目标里加上碳价格,就变成低碳规划;把负荷曲线换成电动车充电负荷,就能研究车网互动。我在项目完成后,又做了两个小扩展:一个是在目标函数里加上碳排放量作为第三个目标,虽然计算时间翻了一番,但帕累托前沿变成了三维曲面,信息量更大;另一个是接入实际某地区的24小时负荷数据,替换掉标准测试系统的负荷曲线,结果发现光伏容量最优位置变化不大,但储能最优配置明显后移,因为实际负荷晚峰持续时间更长。
我个人在实际操作中最大的体会是,规划研究一定要把优化器和评估器解耦。优化器只关心决策变量和目标值,评估器只关心输入方案和输出指标,两者之间用函数接口连接。这样你能轻松替换优化算法,比如换成SPEA2或者MOEA/D,而评估部分不动;也可以替换评估器,比如把序贯蒙特卡洛换成基于N-1扫描的解析法,而优化部分不动。很多研究生项目最后失控,就是因为把寻优逻辑和潮流计算缠在一起,改一个地方牵一发而动全身。
最后分享一个小技巧:每次跑完一代,把当前种群的目标值矩阵保存成CSV,文件名带时间和代数标记。这样即使程序中途崩了,你也可以从最后一代继续跑,不用从头再来。我做项目时有过一次跑了8小时、最后因为磁盘空间不足崩溃的经历,从那以后每代保存成了习惯。优化这种事,最珍贵的不是算法多高级,而是跑一次能从中看到问题的机会,数据丢了才是最痛的。