简介:面向极端天气下电网韧性分析与提升需求的完整代码资源,适用于电力系统科研人员、研究生及电网规划运行工程师。包内共收录一百四十八个文件,包含一百四十个CSV数据文件与八个Python脚本,压缩后约二百四十兆。CSV数据源自BCC-CSM2-MR气候模式SSP126情景下的十二小时调整风速序列,覆盖多个年份,为极端天气场景建模提供基础数据;Python脚本则对应数据处理、韧性评估与优化策略等关键环节,便于用户直接运行和二次开发。目前已吸引一百三十四人学习下载,特别适合需要快速构建极端天气案例库、复现韧性提升算法或拓展自身研究的读者。通过这套代码,可省去繁琐的气候数据采集与清洗工作,专注于韧性指标计算与策略验证。代码与数据配套完整,可直接用于相关课题的复现与扩展研究。 当台风过境、冰灾压断线路的时候,传统配电网的"N-1校验"和"可靠性指标"会瞬间失灵。原因很简单:极端天气下故障不再是一个一个地来,而是成片成片地出现,甚至伴随连锁性跳闸。这几年我在做电网韧性方向的研究和工程落地,最深的体会是——靠人工经验和静态规划根本扛不住这种场景,必须把"韧性提升策略"写成一整套可计算、可调度、可复现的代码框架。这篇文章就把这套思路完整拆出来,从韧性评估建模到深度强化学习调度,再到实测踩坑,一次性讲透。
这个方向适合两类人看:一类是做配电网规划或调度算法研究的工程师和研究生,另一类是刚接触电力系统与强化学习交叉领域、想找一个完整项目入手的开发者。下面所有内容都基于一套能跑通的核心代码框架,我会把设计逻辑和关键代码一并给出。
1. 可靠性失效的极端场景:韧性提升代码到底要解决什么问题
1.1 可靠性与韧性:两个完全不同的博弈尺度
在电力系统里,可靠性关注的是"大概率、小影响"的扰动,比如N-1故障,线路检修,负荷波动。这类问题有成熟的标准和计算工具,学术界和工业界已经玩了半个世纪。但韧性面对的是"小概率、大影响"的极端事件——台风、冻雨、洪水、地震。这类事件的特点是:多重故障同时发生,设备连锁失效,而且恢复时间长,社会影响面巨大。
用一句行业里常说的话来概括:可靠性是"平时少生病",韧性是"大病不死、倒下能爬起来"。极端天气下,电网真正的缺口不是平时那套冗余配置,而是故障后的快速评估和快速恢复能力。也就是说,我们需要一套代码,能够在大规模故障场景下告诉调度员:先合哪个开关、先送哪条线路、分布式电源怎么出力,才能在最短时间内恢复最多的负荷。
1.2 为什么"策略"必须代码化
很多韧性研究停留在概念层面,给出的是一个流程框架或者指标计算表,但真正到工程落地,必须有可执行的决策逻辑。极端天气下的配电网故障场景是典型的组合爆炸问题:一条馈线有几十个分段开关,故障组合动辄上千种,靠人工经验表根本排不过来。
所以韧性提升策略的代码化包含三层含义:
- 状态感知层:根据气象预报和实时量测,生成故障场景,评估系统当前状态。
- 策略决策层:在故障场景中计算最优或次优的恢复调度动作。
- 执行验证层:把决策结果送进潮流计算验证可行性,反复迭代。
这就是一个闭环的"感知—决策—验证"回路。我实际做下来,最务实的路线是把第二层交给深度强化学习,因为它的推理速度快,且能在离线训练阶段充分探索各种极端故障组合。下面就从评估建模开始,一步一步把代码框架搭起来。
2. 韧性评估先行:用pandapower和故障概率模型搭出可计算的基线
2.1 韧性曲线的量化逻辑:面积越小越好
不谈指标谈提升都是空话。工程上最常用的韧性量化方法是系统性能曲线。横轴是时间,纵轴是系统性能(比如健全负荷比例、供电能力),曲线从故障发生开始跌落,到故障清除后逐步回升。这个过程中,曲线与理想水平线围成的面积,就是极端事件造成的总计损失,行业内叫"韧性损失"或者"韧性三角"面积。
面积越小,说明系统跌得少、恢复得快,韧性就越好。这个指标把"预防—抵抗—响应—恢复"四个阶段统一成了一个数字,非常便于用代码实现和对比策略优劣。所以我建议所有做韧性提升策略的人,第一步先把评估函数写出来,否则后面调算法都不知道在优化什么。
2.2 场景建模:用IEEE 33节点系统当试验田
标准配电网测试系统里,IEEE 33节点是使用频率最高的,因为它规模适中,有联络开关,适合做重构与恢复研究。在代码工具选型上,我用的是pandapower,它是目前Python生态里做配电网潮流分析最顺手的一个库,可以直接读取标准网络,也能快速修改线路投运状态。
import pandapower as pp import pandapower.networks as nw import numpy as np # 加载IEEE 33节点配电网 net = nw.create_ieee33_bus() # 给系统加两台分布式电源(光伏/储能),挂在不同母线上 pp.create_sgen(net, 17, p_mw=0.5, q_mvar=0.1, name="PV_1") pp.create_sgen(net, 32, p_mw=0.4, q_mvar=0.1, name="WT_1") # 初始状态下所有线路投运 net.line["in_service"] = True有了这个基础网络之后,就进入关键的故障场景建模。极端天气下线路故障概率和气象强度高度相关,工程上常用风速或冰厚作为驱动变量。下面这个函数模拟了风速与线路故障概率的关系:
def line_failure_probability(wind_speed): """极端风速下的线路故障概率简化模型""" if wind_speed < 25: return 0.001 # 正常运行状态,低概率随机故障 elif wind_speed < 33: # 风速在25~33 m/s之间,故障概率线性上升 return 0.001 + 0.299 * (wind_speed - 25) / (33 - 25) else: return 0.6 # 严重风速下,线路大概率故障 def generate_fault_scenario(net, wind_speed, seed=42): """根据风速对线路进行随机故障抽样""" rng = np.random.default_rng(seed) faulted_lines = [] for idx in net.line.index: p = line_failure_probability(wind_speed) if rng.random() < p: faulted_lines.append(idx) return faulted_lines有了故障场景,韧性评估函数要做的事就是:把故障线路切掉,跑一次潮流,计算系统损失的负荷比例,得到当前时刻的性能值。下面是一个简化版评估逻辑:
def compute_performance(net, faulted_lines): """返回当前系统性能(健全负荷比例)""" net_work = net.deepcopy() net_work.line.loc[faulted_lines, "in_service"] = False try: pp.runpp(net_work, calculate_voltage_angles=True) served_load = net_work.res_load["p_mw"].clip(lower=0).sum() if np.isnan(served_load): return 0.0 total_load = net_work.load["p_mw"].sum() return float(served_load / total_load) except pp.powerflow.LoadflowNotConverged: return 0.0这里有个容易踩的细节:pandapower在某些严重故障下潮流可能不收敛,程序会直接抛异常。在批量仿真时,必须用try/except兜底,不收敛时按最坏情况处理(性能记为0),否则整个训练循环会中断。我最初就是没处理这个,训练十分钟程序就崩了。
2.3 为什么选pandapower而不是Matpower
这个问题我反复被问到。Matpower在MATLAB里确实成熟,但做强化学习训练需要频繁修改网络拓扑、反复跑潮流,Python生态下的迭代速度和数据处理方便程度明显更好。pandapower底层是PYPOWER,计算速度在这个规模下完全够用,而且可以直接配合gym做环境封装,省掉一大坨数据传递代码。
3. 把灾后恢复调度改写成强化学习问题:状态、动作、奖励怎么定
3.1 为什么数学规划在这里不够用
配电网灾后恢复本质是时序决策问题:先恢复哪些负荷、开关怎么操作、分布式电源怎么调度,每一步都会影响后续的恢复空间。用混合整数规划做小规模还可以,一旦节点数上去,求解时间指数增长,而且气象条件一变就得重新建模。强化学习不一样,它把决策过程变成马尔可夫决策过程(MDP),离线训练时把所有能碰到的故障场景都跑一遍,在线调度时只需要一次前向推理,毫秒级出动作。
当然,强化学习的前提是训练阶段要能充分采样各种故障场景。所以环境设计不能只在单一故障上训练,必须在每个 episode 开始时随机抽样一组故障线路,这种训练出来的策略才具备泛化到新场景的能力。
3.2 MDP三件套:状态、动作、奖励的具体设计
状态空间:状态是智能体观察到的"世界"。我取的是:各节点当前有功负荷、各线路投运状态、分布式电源当前出力、故障线路编号的one-hot编码。为了加速收敛,所有连续量都要做归一化,P负荷除以基准功率,电压偏离度单独给一个通道。
动作空间:这是整个设计里最需要斟酌的地方。配电网恢复的动作分两类,一类是离散的开关操作(合/跳联络开关、分段开关),另一类是连续的分布式电源出力调整。我的方案是把开关动作设计成二值动作,用Gumbel-Softmax处理离散采样,同时把DG出力设计成连续动作,用tanh限制在[-1,1]再映射到出力范围。这样混合动作空间既保留了重构的灵活性,又利用了DG的连续调节能力。
下面这段代码是最简版的环境接口:
import gym from gym import spaces class GridRestoreEnv(gym.Env): def __init__(self, net, fault_lines_generator): super().__init__() self.net = net self.fault_lines_generator = fault_lines_generator self.num_line = len(net.line) self.action_space = spaces.Dict({ "switch": spaces.MultiBinary(self.num_line), # 线路开关状态 "dg_p": spaces.Box(low=-1, high=1, shape=(2,)), # DG出力增量 }) self.observation_space = spaces.Box(low=0, high=1, shape=(self.num_line + 6,)) def reset(self): self.fault_lines = self.fault_lines_generator() self.dg_p = np.zeros(2) return self._get_obs() def step(self, action): # 应用开关动作与DG出力 self.net.line["in_service"] = True self.net.line.loc[self.fault_lines, "in_service"] = False self.net.line.loc[action["switch"].astype(bool), "in_service"] = True self.net.sgen.loc[:, "p_mw"] += action["dg_p"] * 0.1 # 潮流计算 try: pp.runpp(self.net) except: return self._get_obs(), -10.0, True, {} # 计算奖励 load_served = self.net.res_load["p_mw"].clip(lower=0).sum() reward = load_served / self.net.load["p_mw"].sum() # 开关动作惩罚,防止乱切 reward -= 0.02 * action["switch"].sum() return self._get_obs(), reward, False, {}3.3 奖励塑形:别让智能体变成"盲人摸象"
奖励函数是强化学习里最敏感的部分。如果只给最终恢复率作为奖励,训练前期会非常稀疏,智能体根本不知道哪些动作是对的。我的做法是:
- 每步都给即时奖励:越接近供电能力上限越好。
- 对开关操作加小惩罚,防止智能体频繁无意义地倒切。
- 对潮流不收敛给大负奖励,防止智能体提出无解的操作方案。
- 增加一个电压越限惩罚项,保证恢复方案满足电能质量约束。
这样奖励信号就不是一条稀疏的终点线,而是一条可感知的"梯度道路",训练稳定性和收敛速度都会明显提升。
4. TD3算法在配电网韧性恢复中的代码落地与训练实录
4.1 选TD3而非DDPG或PPO的原因
恢复调度动作空间包含离散开关和连续DG出力,DDPG对这种混合动作处理不稳定;PPO也能做,但对超参数敏感,调起来比较折腾。TD3(Twin Delayed Deep Deterministic Policy Gradient)在DDPG基础上做了三处改进:双Q网络取最小值抑制过估计、目标策略平滑加噪声提升抗扰动能力、延迟更新Actor减少累积误差。实测下来,TD3在这个场景下收敛速度和稳定性都明显好于DDPG,训练3000个episode就能看到稳定的恢复率提升。
TD3的核心逻辑用伪代码表示就三件事:
# 1. Actor:输入状态,输出动作 actor = Actor(state_dim, action_dim) # 2. 两个Critic:分别估计Q值,取最小值 qf1 = Critic(state_dim, action_dim) qf2 = Critic(state_dim, action_dim) # 3. 更新策略延迟:每更新2次Critic才更新1次Actor if update_count % policy_delay == 0: actor_loss = -qf1(state, actor(state)).mean() update_actor(actor_loss)4.2 关键实现细节:目标策略平滑
TD3的"目标策略平滑"在配电网恢复场景里很关键。极端天气下的故障场景变化剧烈,状态跳变幅度大,如果目标值直接由确定性策略网络算出,Q值估计很容易发散。加入噪声平滑后,目标值不再是一个尖锐点,而是一个邻域的期望,训练稳定性大幅提升。
def soft_update_target(net, target_net, tau): for param, target_param in zip(net.parameters(), target_net.parameters()): target_param.data.copy_(tau * param.data + (1.0 - tau) * target_param.data)实际训练中,我设tau=0.005,目标策略平滑噪声取0.2,噪声裁剪范围[-0.5, 0.5]。这几个参数在配电网场景下表现最稳定,但每个系统的最优值略有差异,建议训练前先跑100个episode观察一下Q值曲线。
4.3 训练循环与收敛监控
训练主循环的核心是"环境生成故障—智能体决策—潮流验证—计算奖励—存入经验池—采样更新"。这里有一个非常值得注意的点:经验回放池不要只用最新数据,要保留足够多的历史场景。因为故障场景本身是随机生成的,如果经验池太小,后期全是某一种故障类型,策略会被带偏。
我用的经验池容量是200000条,单批次采样256条,学习率Actor和Critic都设为3e-4,Adam优化器。训练过程中主要盯三个指标:
- 平均奖励曲线:是否单调上升并趋于平稳。
- 平均恢复率:每个episode结束后,把恢复的负荷比例记录下来,这个是业务指标,更直观。
- Q值估计与真实回报的差值:如果Q值持续高估,说明双Q机制没有正常工作,需要调低学习率或加大平滑噪声。
下面这个曲线记录代码是训练过程中的"仪表盘":
def log_training(rewards, recovery_rates, episode): print(f"Episode {episode} | avg_reward={np.mean(rewards[-50:]):.4f} " f"| avg_recovery={np.mean(recovery_rates[-50:]):.4f}")训练到2500个episode左右,我这边系统的平均恢复率稳定在0.92以上,相比随机策略的0.55有了非常明显的提升。这时候再拿一组训练中没见过的极端故障场景做测试,验证泛化能力,如果恢复率依然在0.85以上,说明这个策略是真正学到了东西,而不是死记硬背训练样本。
5. 实测中最容易翻车的三个环节:收敛、奖励与动作映射
5.1 潮流计算不收敛:被单独挂掉的训练循环
这个问题我在第2章提过一嘴,但它是实测里出现频率最高的坑,必须多说几句。恢复过程中,智能体经常会尝试打开一些开关,导致网络解列成孤岛,部分孤岛没有电源支撑,潮流计算直接报错。
我在环境里加入了一个预检查逻辑:如果动作后的网络连通分量中存在"没有电源节点的孤岛",直接判定动作无效,给负奖励。这个逻辑避免了大批无效潮流计算,把每一步的仿真时间从几十毫秒降到十几毫秒。
from pandapower.auxiliary import _pd2ppc import networkx as nx def check_island_without_source(net): """检测是否存在无电源孤岛""" g = nx.Graph() g.add_nodes_from(net.bus.index) for _, row in net.line[net.line["in_service"]].iterrows(): g.add_edge(row["from_bus"], row["to_bus"]) for island in nx.connected_components(g): buses = list(island) if not net.sgen[net.sgen["bus"].isin(buses)].empty: continue if (net.ext_grid["bus"].isin(buses)).any(): continue return True return False5.2 奖励函数权重失衡:恢复率上去了,电压却超限了
初期我给的奖励过分强调恢复负荷量,结果智能体学到一个投机取巧的坏毛病——把所有DG出力推到最大,电压越限也无所谓,因为潮流计算还是能收敛的。这个策略虽然恢复率指标好看,但工程上毫无意义。
解决办法是把电压约束从"惩罚"升级为"硬约束"。当检测到任何节点电压越限时,奖励直接乘以0.1,同时把这个episode的标志置为"不可行方案",在记录时单独统计。经过这个调整,最终策略在恢复负荷的同时,把电压偏差控制在5%以内。
5.3 混合动作空间映射:连续量到离散量的"翻译"
gym.spaces.Dict虽然支持混合动作,但TD3的Actor网络直接输出混合结构并不方便。我的实用做法是让Actor输出一个连续向量,然后用滑动阈值把前n个维度映射为二值开关动作:大于0.5的置1,表示合闸;小于等于0.5的置0,表示跳闸。DG出力则直接用后两个维度的值映射。
这个映射相当于给离散量加了一层"软决策",保证了梯度可以回传,又保持了离散动作的语义。你还可以用温度系数来控制这个映射的锐度,温度越低越接近真正的离散采样。实际测试中,温度设为0.8时训练最稳定。
def action_mapping(raw_action, threshold=0.5, temperature=0.8): switch_logits = raw_action[:num_line] switch_probs = torch.sigmoid(switch_logits / temperature) switch_action = (switch_probs > threshold).int() dg_action = torch.tanh(raw_action[num_line:]) return switch_action, dg_action6. 还想继续扩展的方向与一点点个人体会
目前这套框架已经能实现"极端天气故障场景生成—韧性评估—TD3恢复调度"的完整闭环,代码结构全部模块化,换一个配电网系统只需要替换网络模型和参数,不需要改算法逻辑。我自己用来做验证的系统除了IEEE 33节点,还跑过IEEE 123节点和某个实际馈线改造后的数据,迁移成本都很低。
如果往更深处扩展,有几个方向我认为非常有价值:一是把气象预报的时序数据接入状态空间,让策略在故障发生前就能做预调度,这是"主动韧性"的范畴;二是用Transformer或BiLSTM替代当前的MLP状态编码器,增强时间序列特征的提取能力;三是把动作空间扩展到储能充放电、微网黑启动等更细粒度的设备维度。
最后说一点个人体会。做这个项目最大的教训是"别急着上算法"。我一开始觉得TD3写过很多次了,直接怼上去就行,结果因为环境建模的细节漏洞太多(不收敛、奖励失衡、动作映射粗糙),光排查环境就花了两周。后来老老实实把评估指标和基线策略先写好,再跑强化学习,整个过程顺利了很多。韧性提升不是拍脑袋定策略,而是把"极端事件响应"变成一个可迭代、可回测的工程问题,代码框架才是底盘。希望这篇拆解能帮你省下那些我走过的弯路。
本文还有配套的精品资源,点击获取