混合配电系统的规划问题,这几年在电网研究里一直是个热门方向。原因很直白——传统配电网是交流单向供电,现在光伏、储能、直流负荷大规模接入,网架结构变成了交直流混合、多源互联的复杂形态,方案选得好不好,直接关系未来几十年的投资成本、运行费用和供电可靠性。这个项目就用Python做了一套完整的东西:构建经济性与可靠性双目标规划模型,用多目标优化算法求解Pareto前沿,再对候选方案做可靠性评估。适合正在做配电网规划、可靠性方向课题的研究生,以及想用Python把电力系统优化从理论落到代码的工程师。
1. 混合配电系统双目标规划的本质:投资、运行与可靠性怎么权衡
1.1 混合配电系统的典型结构与规划对象
混合配电系统,在工程文献里最常见指的是交流—直流混合配电网。它不像传统配电网那样只有交流母线,而是在系统中段或末端引入直流母线,通过电压源换流器(VSC)把交流和直流两个子网连接起来。交流侧保留常规的配电变压器、交流馈线和交流负荷;直流侧接入光伏、储能、直流充电桩这类本质上就是直流输出的设备,减少AC/DC变换次数。好处是明显的——光伏发的直流电不需要先逆变成交流,再整流回直流供直流负荷,能省掉一到两级变换损耗,整体效率可以提高3%到5%。
那么规划的对象到底是什么?无非三类:第一是线路,要不要新建或改造某条馈线,扩容到多大;第二是VSC,在哪些节点装换流器、容量选多大;第三是分布式电源与储能,在候选节点装多少光伏、储能容量。这三类决策相互耦合——VSC装多了成本高但直流侧与交流侧互济能力强,可靠性改善明显;DG装多了经济性可能反而不划算,因为初期投资大且运行约束复杂。所以这是一个典型的组合优化问题,决策变量一部分是0/1选择,一部分是连续容量。
1.2 经济与可靠性双目标为什么不能合成一个单目标
这里先问一句"为什么必须是双目标"。把两个指标加权成一个目标当然简单,比如f = w1f1 + w2f2,但权值w1、w2谁说了算?实际上很难拍脑袋定。预算紧的单位可能更在意初期投资,医院、数据中心这些用户又对可靠性极度敏感,权重应该完全不同。而且经济性和可靠性量纲不同——一个是万元/年,一个是MWh/年,直接加权缺少物理意义。
更重要的是,这两个目标之间是强冲突的:花钱多、网架强、设备冗余多,可靠性自然好;反过来,绝大多数的规划方案在成本—可靠性平面上会形成一条类似"弯弓"的前沿,你不可能在不增加成本的前提下无限制提升可靠性。单目标加权只能求出一个折中点,换一组权重就要重算一遍,而且无法回答"我多花一千万能换多少可靠性"这个问题。多目标优化一次性给出Pareto前沿,解集里每一个点都是"不支配"的,决策者拿着这条前沿曲线去谈预算、谈可靠性指标,才能做真正的工程决策。
1.3 双目标规划的整体研究框架梳理
这个项目的整体框架可以概括为"外层寻优,内层评估"八个字。外层是多目标优化算法(NSGA-II为代表),负责在搜索空间里生成候选规划方案;内层是两个评估模块——经济性评估和可靠性评估。经济性评估相对简单,大部分成本项可以由设备参数直接计算,但网损成本需要潮流计算,所以必须把潮流模块包进去;可靠性评估则更重,需要模拟元件故障与修复过程,统计停电指标。
我项目的实现流程就是这样:先生成初始种群→每个个体解码得到拓扑与容量→调用潮流计算,算出网损与电压分布→调用序贯蒙特卡洛模块,模拟一年时序故障场景得到EENS→两个目标返回值交给NSGA-II做非支配排序、拥挤距离计算→通过锦标赛选择、交叉、变异产生下一代→循环直到收敛,最后输出Pareto前沿和对应方案。听起来不复杂,但每一步都有大量工程细节,下面逐个展开。
2. 双目标规划模型的数学表达:目标函数、约束与可靠性的耦合
2.1 经济性目标的完整构成与等年值折算
经济性目标我按工程界通行的"年综合费用"来写,单位是万元/年:
min f1 = C_inv等年值 + C_ope + C_loss + C_ENS
第一项是投资成本的等年值。一次性投资的设备不能把全部费用算在当年,否则对不同寿命设备没法比较,所以要用资金回收系数CRF摊到每一年。CRF = r(1+r)^Y / ((1+r)^Y - 1),r是折现率,Y是设备寿命。比如折现率8%,设备寿命20年,CRF是0.1019左右,也就是说1000万设备投资,等年值大约是101.85万元。这个折算贯穿所有投资项:新建线路按单位长度造价乘长度,VSC按容量造价,光伏与储能按装机容量造价,分别用各自的寿命年限折算后再相加。
第二项是年运行费用,包括向上一级电网购电的费用、DG的燃料与维护费用。这个需要潮流模块配合:先计算典型日或全年的负荷与DG出力曲线,做时序潮流,得到逐时段购电功率、网损功率,再乘以电价累加。第三项网损成本也是一样,由潮流结果统计。最后一项C_ENS是停电损失,它是把可靠性评估结果——即EENS(期望缺供电量)——乘以单位电量停电损失费用得到的,这是经济性和可靠性在目标函数层面的第一次耦合。有人会问,EENS已经在可靠性目标里了,为什么经济目标还要再算一次?因为停电损失确实是经济账的一部分,双目标里另一个目标直接用物理指标EENS,两者从不同维度刻画同一个问题的两个方面。
2.2 可靠性目标的选用:EENS、SAIDI、SAIFI怎么算
可靠性目标我直接选EENS作为第二目标,因为它是一个有连续性的电量指标,带概率期望性质,适合作为优化目标去最小化。如果是运行可靠性报表,大家更习惯用SAIDI和SAIFI这两个指标。SAIDI是系统平均停电持续时间,等于所有用户停电时间总和除以用户总数;SAIFI是系统平均停电频率,等于停电用户次数的总和除以用户总数;EENS则是把每次停电的削减电量按概率加权求和,单位MWh/年。模拟中我们这样统计:每次系统失效状态持续时间为T小时,期间削减的负荷功率是P_curt,那么这个状态的缺供电量就是P_curt * T,所有状态累加后除以模拟总年数得到EENS。SAIDI和SAIFI可以用类似方式统计,作为评估结果报表的辅助指标输出。
为什么优化目标用EENS而不是SAIDI/SAIFI?因为后两个指标只反映"停了多少次、停了多久",而忽略了"损失了多少负荷"。在规划阶段,我们最关心的恰恰是缺供电量的期望值——一次停掉50MW的馈线和一次停掉200kW的支线,对系统的影响完全不同,EENS能把这个区分开。
2.3 约束条件清单与可行性判定逻辑
约束条件我整理成一张表,完整约束包括:
- 交流节点功率平衡约束
- 直流节点功率平衡约束
- 节点电压上下限约束
- 支路载流量上限约束
- VSC传输容量上限约束
- DG安装容量上限约束
- 储能SOC与充放电功率约束
- 配电网辐射状拓扑约束
- 馈线分段容量约束
- DG渗透率上限约束
其中,辐射状拓扑约束非常关键。配电网不同于输电网,正常运行要求是辐射状的,闭合的多环会带来保护配合复杂化和短路电流超标。规划搜索中一旦出现环网,就要用图论破环算法处理:要么在解码时判断、拒绝产生环网的决策序列,要么对环网个体加很大的惩罚值。惩罚值的方式实现快,但可能让搜索走弯路;我更推荐在解码阶段就做约束修复——用深度优先搜索检查连通性,把多余联络线强制切除,保证每一个进入评估阶段的方案都是合法的辐射状网络。
可行性判定逻辑方面,我的做法是"先判定,再评估"。解码后的方案先做连通性检查,不连通或成环的直接打上不可行标记,不进入潮流和蒙特卡洛计算,这样省掉大量无效计算。电压和容量约束则在潮流计算后检查,如果有越限,可以尝试调整DG出力或切负荷,实在越限就判该方案电气不可行。蒙特卡洛模拟中的切负荷只针对故障后场景,正常状态必须运行在可行域内。
3. 可靠性评估方法选择:从故障枚举到时序蒙特卡洛
3.1 解析法与模拟法的适用边界
可靠性评估在方法上分两大流派:解析法和模拟法。解析法的代表是故障枚举、最小割集、可靠性框图,思路是把系统所有可能的故障状态列出来,计算每个状态的概率并加权统计指标。它的好处是计算快、结果确定,适合节点不多、网络结构简单的系统。缺点是随着元件数量增加,状态数指数爆炸,而且很难考虑时序负荷曲线和DG出力时序——光伏晚上不发电、夏天空调负荷高峰期故障和凌晨故障,影响完全不一样。
模拟法这边,非时序蒙特卡洛按系统状态的联合概率分布抽样,不考虑时间先后;时序蒙特卡洛则精确模拟每个元件的"故障—修复—再故障"循环,按时间轴推进。对混合配电系统这种带储能、带分布式电源、带时序负荷的系统,我强烈推荐时序蒙特卡洛,原因是储能的充放电策略和DG出力都强依赖时间序列,非时序方法根本没法建模这个逻辑。三类方法的一个对比整理在下面:
| 评估方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 解析法/故障枚举 | 精确、快、无随机误差 | 状态爆炸、难以刻画时序 | 小规模网络、拓扑简单 |
| 非时序蒙特卡洛 | 可处理复杂元件相关性 | 无法建模时序策略 | 元件相关性敏感的分析 |
| 时序蒙特卡洛 | 精确建模时序、储能、DG | 计算量大、收敛慢 | 混合配电系统规划评估 |
3.2 时序蒙特卡洛的实现逻辑与伪代码
时序蒙特卡洛的核心逻辑分三步。第一步,基于元件可靠性参数抽样生成可靠性时序。每个元件(线路、变压器、VSC、DG)都有故障率λ(次/年)和平均修复时间MTTR(小时),假设故障间隔与修复时间服从指数分布,那么按公式TTF = -1/λ * ln(U),TTR = -MTTR * ln(U)抽样,其中U是(0,1)均匀随机数。对每个元件分别生成一个长期的状态时间表:从运行到故障、故障到修复、再运行再故障……无限延续。
第二步,把所有元件的状态时间表按时间轴合并,得到系统级的"时序状态序列"。这类似于把多条事件流做归并排序,每个时间断面记录当前处于故障状态的元件集合。对每个故障时间断面,运行潮流分析,判断是否存在支路过载或电压越限;若有,就启动负荷削减流程。第三步,统计可靠性指标,累计每个故障状态的停电时长、削减功率,最后除以模拟年数得到EENS、SAIDI、SAIFI。
def sequential_monte_carlo(system, sim_years=500): # 1. 为每个元件生成状态时序 comp_events = [] for comp in system.components: t = 0.0 while t < sim_years * 8760: # 小时为单位 ttf = -1.0 / comp.failure_rate * np.log(1 - random()) t += ttf ttr = -comp.MTTR * np.log(1 - random()) comp_events.append((t, comp.id, 'fault')) t += ttr comp_events.append((t, comp.id, 'repair')) # 2. 按时间排序生成系统状态序列 events.sort(key=lambda x: x[0]) # 3. 扫描每个故障事件,判断是否需要切负荷 failed = set() for t, comp_id, action in events: # 更新failed集合... if action == 'fault': failed.add(comp_id) # 对该状态做潮流+切负荷分析 curtailed = evaluate_failure_state(system, failed) record_statistics(t, failed, curtailed) else: failed.remove(comp_id) # 4. 汇总指标 return compute_reliability_indices(statistics)这里要提醒一个细节:模拟时长建议在500到1000年等效时间之间。别被这个数字吓到,这里的"年"是虚拟的,实际计算就是用小时数乘以仿真次数,本质是抽取足够多样本。元件故障率高的系统,比如馈线故障率0.1次/年,模拟500年会遇到约50次故障事件,统计结果才比较稳定。如果你的系统可靠性特别高(故障率极低),500年可能遇到的失效事件太少,EENS方差会很大,这时需要加大模拟量或使用方差缩减技巧。
3.3 切负荷策略:怎么把失效状态变成可靠性指标
切负荷策略是可靠性评估里最体现工程经验的环节。故障发生后,不是简单地"全切",而是要以最小代价恢复约束满足。我常用的策略分三级:第一级,优先削减可中断负荷。在规划模型中,可以把一部分负荷标记为可中断负荷,例如工业非关键负荷、可调节温控负荷,它们被切除的成本低甚至为零。故障时先切这部分,削完再校验支路潮流和节点电压。
第二级,按重要度从低到高削减普通负荷。系统中会有一些用户的停电损失较小,从这些节点开始切,避免影响医院、重要用户。第三级,如果故障非常严重,主干线路或换流器整体失效,那就只能按比例削减末端负荷甚至全馈线切除。
从计算角度看,切负荷的迭代流程是:故障状态潮流计算→找出越限支路/节点电压→按优先级确定削减节点与削减量→更新负荷、重新潮流→重复直到约束全部满足。这个循环通常3到5次就收敛。切负荷结果中,各节点被削减的功率乘以该状态持续时间,累加后就是EENS的分子。
4. Python代码实现:从网络建模到多目标优化的完整拆解
4.1 环境准备与项目目录结构
先确认Python环境。推荐使用Python 3.8以上版本,64位。项目依赖的库包括numpy、pandas、matplotlib,用pip安装即可:
pip install numpy pandas matplotlib如果不想完全手写优化算法,也可以装pymoo或deap这类现成库,但我个人的建议是NSGA-II的核心自己手写一遍,篇幅不大,还能完全掌控种群编码和约束处理逻辑。用现成库时,自定义编码、自定义约束和自定义交叉变异会比较别扭。目录结构我按模块划分:
distribution_planning/ ├── data/ # 网络参数、负荷、DG出力曲线 │ ├── bus.csv │ ├── line.csv │ ├── load_profile.csv │ └── dg_options.csv ├── models/ # 对象定义 │ ├── network.py # Node / Line / VSC / DGS 定义 │ └── system.py # 系统类,组合以上对象 ├── pf/ # 潮流计算 │ └── forward_backward.py ├── reliability/ # 可靠性评估 │ └── sequential_mc.py ├── optimization/ # 优化算法 │ └── nsga2.py ├── scripts/ │ └── main_planning.py # 主程序 └── results/ # 输出Pareto前沿图、指标表这个结构没有复杂框架,纯粹是用Python的class把配电网对象化,逻辑清楚、便于调试。初学者最怕的就是一上来就写面向过程的一坨脚本,后面加一个目标都寸步难行。
4.2 配电系统数据建模:节点、线路、DG与负荷怎么组织
我用类的方式来组织网络数据。节点类需要保存节点类型(交流/直流/VSC联络)、电压等级、基准电压、负荷功率;线路类保存首末端节点、阻抗、载流量和建设状态;VSC类保存容量、损耗系数和换流站造价单价;DG类保存类型(光伏/储能/燃气)、额定容量、出力时序。系统类则把所有元件组合起来,并提供拓扑查询接口。核心代码可以这样写:
import numpy as np from dataclasses import dataclass @dataclass class Bus: id: int bus_type: str # 'ac' 或 'dc' 或 'vsc_ac' / 'vsc_dc' v_base_kv: float # 基准电压kV p_load: float # 有功负荷kW q_load: float # 无功负荷kVar(交流节点) p_dg: float = 0.0 # DG注入有功kW q_dg: float = 0.0 # DG注入无功kVar @dataclass class Line: id: int f_bus: int t_bus: int r: float # 电阻Ω x: float # 电抗Ω capacity_kva: float # 载流量上限kVA build_cost: float # 工程造价元/km/条 built: bool = False # 当前是否投入 @dataclass class VSC: id: int ac_bus: int # 交流侧接入节点 dc_bus: int # 直流侧接入节点 capacity_kva: float # 换流容量 loss_coef: float # 损耗系数这里的教训是:数据类型一定要用单位标明清楚,建议在字段名里带上_kw、_kv这类单位后缀,否则后面数据对接的时候,一个量纲错误能让潮流算出来的电压飞到天上。这是我实际踩过的坑——有一次VSC容量单位我用了MVA,潮流模块里却按kVA读,结果换流器全部越限,Pareto前沿解集直接变形。
4.3 前推回代潮流计算的实现细节
配电网潮流计算我选用前推回代法,不选牛顿-拉夫逊法。原因是配电网辐射状结构天然适合前推回代:回代计算支路电流、前推更新节点电压,不需要构造雅可比矩阵,不用解高阶线性方程组,内存占用低,迭代一次只做两次遍历,而且对平启动初始电压的容忍度很高——配电网经常带重负荷,用牛顿法从平启动开始经常不收敛,前推回代只要拓扑没问题,几乎都能算出来。
迭代逻辑是:初始化所有节点电压为1.0∠0°(标幺值)→从末梢向根节点回代,根据节点电压和注入功率求支路电流→从根节点向末梢前推,用支路电流和线路阻抗更新节点电压→比较两次迭代电压差,最大值小于ε就停止。
def backward_forward_power_flow(network, max_iter=50, tol=1e-6): buses = network.buses lines = network.lines n = len(buses) V = np.ones(n, dtype=complex) # 平启动 for it in range(max_iter): V_old = V.copy() # 回代:计算各支路电流(辐射状,用DFS从叶节点向根推) I_line = {} for line_id in network.dfs_order_reverse(): # 末梢优先 f, t = lines[line_id].f_bus, lines[line_id].t_bus s_load = buses[t].p_load + 1j * buses[t].q_load s_dg = buses[t].p_dg + 1j * buses[t].q_dg I_inj = np.conj((s_load - s_dg) / V[t]) # 累加该节点的子支路电流 for child in network.children[line_id]: I_inj += I_line[child] I_line[line_id] = I_inj # 前推:更新电压 for line_id in network.dfs_order_forward(): # 根节点优先 f, t = lines[line_id].f_bus, lines[line_id].t_bus z = lines[line_id].r + 1j * lines[line_id].x V[t] = V[f] - z * I_line[line_id] if np.max(np.abs(V - V_old)) < tol: break return V这段代码是教学简化版,实际工程中还要处理DC子网、VSC两端控制模式和变压器变比非标幺问题。混合配电系统里,交流子网和直流子网要用统一基准值系统换算,VSC作为交直流的接口,一侧给定有功功率、另一侧控制电压,潮流交叉迭代时要把两套子网循环求解,收敛判据的ε选1e-5到1e-6比较合适。
4.4 NSGA-II优化器:非支配排序与拥挤距离的选择机制
多目标优化我选了NSGA-II。理由有三:它是多目标进化算法里最成熟、最容易工程化实现的框架;种群搜索对离散+连续混合编码处理自然;可扩展性好,后面想加入网架重构、储能时序策略,只需改编码和解码模块。算法核心分为四块:快速非支配排序、拥挤距离计算、锦标赛选择、SBX交叉与多项式变异。
非支配排序的目的是把种群划分成一层一层的Pareto前沿。个体i支配个体j,要求i在全部目标上不劣于j且至少有一个目标严格优于j。排序后,第一层是当前种群中不受任何个体支配的一组解,第二层是去掉第一层后剩余种群的非支配解集,以此类推。选择时优先保留层级靠前的个体,同一层级内优先保留拥挤距离大的个体,这样既能保证收敛又能维持种群多样性。
def fast_non_dominated_sort(obj_vals): n = len(obj_vals) dominated_count = [0] * n dominated_list = [[] for _ in range(n)] front = [[] for _ in range(n)] rank = [0] * n for i in range(n): for j in range(n): if i == j: continue if dominates(obj_vals[i], obj_vals[j]): dominated_list[i].append(j) elif dominates(obj_vals[j], obj_vals[i]): dominated_count[i] += 1 if dominated_count[i] == 0: front[0].append(i) rank[i] = 0 k = 0 while front[k]: q = [] for i in front[k]: for j in dominated_list[i]: dominated_count[j] -= 1 if dominated_count[j] == 0: rank[j] = k + 1 q.append(j) k += 1 front[k] = q return front[:k]这里要注意:目标函数值必须是"越小越好"的一致方向。如果经济性目标f1是万元/年,可靠性目标f2是EENS,都是越小越好,没问题。如果程序里有人把投资表达成收益(越大越好),排序函数会直接反转,这是初学者最容易犯的错。
拥挤距离计算是对同一前沿层内的个体,按每个目标排序,对相邻个体目标值做归一化差分,边界个体给无穷大距离,之后求两个目标的距离之和。距离越大,代表该解在目标空间越孤立,越值得保留,避免所有解聚成一坨。这也是为什么NSGA-II能比较均匀地铺满整条Pareto前沿。SBX交叉和多项式变异不是必须自己实现,但我建议至少要理解它们的分布指数。交叉分布指数ηc=20、变异分布指数ηm=20是常用起点,这两个参数决定子代离父代的远近。ηc越大,子代越接近父代,搜索更精细;ηm越大,变异步长越短。种群规模建议100到200,迭代次数200到500,具体看网络规模。我遇到过迭代代数设到300但前沿形状几乎没变化,也遇到过只跑80代就收敛到局部前沿的情况,建议每次跑完把收敛曲线打出来看一眼再结束。
4.5 可靠性评估模块与优化搜索的耦合方式
可靠性与优化搜索耦合是整个项目计算量最大的瓶颈。一个种群200个个体,每个个体都要跑一次500年时序蒙特卡洛,如果每次评估要算1000个故障状态,总计算量就是200乘1000等于20万次潮流求解,这还没算迭代次数。如果不做优化,直接暴力算,基本跑不动。
所以我的耦合方式做了三层加速。第一层,候选方案先做连通性和约束预判,不满足的个体直接跳过可靠性评估,给它们赋一个极大EENS值,不占用蒙特卡洛时间。第二层,同一个个体的可靠性评估结果做缓存,用方案编码的哈希值作为key,因为NSGA-II在迭代过程中子代会大量重复父代的基因片段,虽然网络拓扑不同,但解码后可能有一部分方案完全相同。第三层,蒙特卡洛内部采用并行抽样,把模拟年数拆成多段,用multiprocessing或ray分发到多核,比如4核机器可以提速3倍上下。如果还是慢,还有一个降级方案:先用解析法粗筛,把明显差的方案剔除,只对候选的少数方案跑完整蒙特卡洛。
这里多说一句,可靠性评估和潮流计算的嵌套不要做反:正确顺序是正常状态潮流(算网损、算经济目标)→故障状态枚举(算可靠性目标)。有些同学把潮流放在蒙特卡洛最内层,逐小时求解,导致计算量爆炸。实际上时序蒙特卡洛只需要在状态发生切换的事件点上做潮流分析,连续运行时段内的电压可以认为近似不变,不需要逐小时计算。
5. 实操过程复盘:常见报错、结果异常与排查技巧
5.1 潮流计算不收敛的典型原因与处理
前推回代法按理说很稳,但我在调试中还是踩过几回不收敛的坑。最常见的原因是辐射状拓扑检查没做好,网络里悄悄存在环流,前推回代会来回振荡不收敛。解决办法是严格DFS判环,在解码阶段就把环破坏掉。第二个常见原因是DG出力设置过大,某条馈线末端反向注入功率,造成局部电压偏高甚至超过1.1标幺,潮流收敛但结果不合理——不一定是算法问题,而是方案本身电气上不可行,要检查DG渗透率约束。第三个原因是线路参数单位错误,比如把欧姆写成了毫欧,阻抗小到几乎为0,潮流的电流会大得离谱,迭代很容易发散。查这类问题我通常打印各节点电压幅值,看看是全局飞升还是局部异常,能快速缩小范围。
收敛性还有一个细节:当迭代次数到了max_iter还没收敛时,不要直接判方案不可行。先用最终电压算一下最大电压偏差,如果偏差只有1e-3量级,说明只是收敛精度设得太严,实际方案是可用的;如果偏差0.1以上,那才是真不收敛。
5.2 蒙特卡洛评估结果波动大怎么办
蒙特卡洛最大的痛点是结果带随机性。同一个规划方案,两次运行EENS结果可能差10%以上,这就让NSGA-II没法稳定比较两个个体——今晚跑出的Pareto前沿和明早跑出的不一样,没法写论文。我解决这个问题从三个方向入手。第一,增大模拟年数。EENS相对误差大致与模拟年数的平方根成反比,500年比200年的方差小得多。但这不是唯一手段,因为模拟年数翻倍计算量也翻倍。
第二,使用方差缩减技术。最简单有效的是公共随机数——在同一轮优化迭代中,让所有个体共享同一套随机种子,这样个体之间的比较在同一批随机场景下进行,相对排序更稳定。这个技巧见效极快,强烈推荐。第三,对低失效概率的元件做重要抽样,提高故障事件的采样概率,再用权重补偿还原概率分布,这个实现复杂一些,但效果明显。
另外,EENS指标在数值上可能非常小,比如只有几十千瓦时,而经济目标可能是几百万元。两个目标不在一个量级,NSGA-II的支配比较虽然不受量纲影响,但在计算拥挤距离时归一化是必要的,否则数值大的目标会主导拥挤度计算,解集分布会偏科。
5.3 多目标解集分布差:参数调优的经验
Pareto前沿分布差,通常要怀疑三件事:种群多样性不足、交叉变异参数不当、约束惩罚过重。先说多样性:如果种群初始只用随机0/1编码,可能会产生大量不可行个体,经过几代筛选后有效个体寥寥无几,前沿自然稀稀拉拉。我建议初始化时用"最小生成树+随机扰动"的方法生成初始网架,保证初始种群里大部分个体都是连通的,这样搜索起点就健康很多。
交叉变异参数方面,二进制选址部分变异率建议取1/n_var左右,连续容量部分用SBX交叉,实数部分用多项式变异。如果发现前沿集中在少数几个点,说明交叉率太低或者选择压力太大,把交叉率从0.8提到0.95、拥挤距离的保留比例提上去,一般能改善。如果前沿在目标空间延伸不开,可能是变异分布指数ηm太大,变异步长太小,搜索只能在小范围打磨,把ηm降到5到10试试。
最后是约束惩罚。我在前面说过,解码阶段优先做约束修复,而不是靠罚函数,因为罚函数引入的不可行解的梯度信息非常不可靠。如果非得用罚函数,惩罚系数不要设得太大,否则种群会被"不许越界"支配,搜索完全丧失探索性,前沿连形状都出不来。
5.4 单位混乱与数据一致性检查清单
单位问题可以说是电力方向Python项目的头号杀手。我在项目里定了一个铁律:所有输入数据的单位在dataclass字段名里写明,所有计算全部用标幺值。潮流用标幺值,可靠性评估的功率、电量也用同一基准,最后输出报表时再换回有名值。这样做的好处是,所有元件无论电压等级是10kV还是0.4kV,都统一到同一个标幺系统里,不会出现量纲错配。
数据一致性检查清单我放在代码入口处:检查所有节点电压基准值一致,检查线路阻抗是否与基准电压匹配,检查DG出力上限是否在节点允许范围内,检查负荷数据的峰谷曲线时间长度是8760小时还是365天,检查VSC两侧母线的交流侧和直流侧容量是否匹配。这些检查写成assert语句,启动主程序时自动过一遍,能省下大量调试时间。有一次我把负荷曲线写成了365个点表示365天平均值,结果全年电量算出来差了一个数量级,排查了整整一天才找到。
最后聊几句实操心得。这个项目做完,我最深的一个体感是:双目标规划的难点根本不在数学公式,而在工程一致性。目标函数谁都会写,NSGA-II代码半小时能敲完,但要让潮流、可靠性、优化三个模块的数据完全对齐,让每一个进入评估的个体都合法且可解释,这才是真正耗时间的地方。所以如果你的项目刚起步,我建议不要急着跑完整流程,先把潮流和可靠性两块单独验证——用一个小网络手算几组结果,确认数值正确,再搭NSGA-II的外壳。这一步做好了,后面所有问题都只是调参和时间问题。可靠性评估与优化耦合的加速策略、公共随机数技巧,是我个人最推荐优先落地的两件事,性价比极高。项目本身还有很大的扩展空间,比如把光伏出力的不确定性用概率场景描述,加入储能充放电时序策略,或者接入真实的配网自动化数据,都是在当前框架上不太费劲就能加进去的方向。祝你的项目跑得顺,天天都能画出漂亮的Pareto前沿。