拓冰建站拓冰建站
首页 / 资讯中心 / 正文

用Python实现电力系统暂态稳定仿真:从转子运动方程到等面积定则验证

简介一份基于Python的暂态稳定仿真程序资源包以PYPOWER-Dynamics扩展为核心面向电力系统工程师、研究人员及高校学生用于分析电网遭受短路、切机等大扰动后的动态行为。包体共85个文件主要包括Python源码、同步发电机模型数据mach/rcd/evnt/dyn、PNG仿真曲线图和说明文档整体仅472KB目录清晰方便按模块查阅与二次开发。资源内置单机无穷大系统SMIB、九节点系统等典型算例覆盖经典/四阶/六阶发电机模型、励磁与调速器控制、故障设置、龙格-库塔与Heun法等数值求解过程并提供电压、功角、频率等响应曲线的可视化脚本。读者可借此掌握暂态稳定仿真的完整建模思路理解各元件动态方程与求解算法并基于开源库进一步拓展自定义模型与场景。已有186人学习下载适合需要开展电力系统动态仿真教学、课题研究或工程预研的读者。1. 暂态稳定仿真程序在仿什么先把转子运动方程讲透从网上下载一个名为“基于Python的暂态稳定仿真程序”的zip包解压后最常见的情况是有main.py、有data目录、readme只有三行。别急着双击main.py先想清楚这个程序要回答的问题——电力系统在某个地点发生短路、切除一条线路、甩掉一台机组之后系统里几十台发电机的转子还能不能继续同步旋转。这个问题的数学核心不是潮流方程而是一组二阶常微分方程即转子运动方程。用Python写暂态稳定仿真本质上就是把这组方程按事件序列做数值积分再根据功角曲线判断是否失稳。这个任务传统上属于BPA、PSASP等商业程序但Python生态里的numpy、scipy、pandas和Numba已经把路铺好。这篇文章面向想把暂态稳定算例从商业软件迁移到开源环境的工程师也面向正在做电气专业课程设计的学生下面给出的方案是业内常见做法先跑通单机无穷大系统再做数据解析、性能优化和结果验证。2. 用Python实现暂态稳定仿真的最小闭环单机无穷大系统摇摆曲线2.1 转子运动方程和功角概念先对齐电力系统暂态稳定分析的起点是同步电机的转子运动方程。忽略励磁调节器和调速器动态保留机械旋转部分方程写成dδ/dt ω_s(Ω - 1)dΩ/dt (P_m - P_e - D(Ω - 1)) / (2H)其中δ是发电机转子相对同步旋转坐标系的功角radΩ是转子转速的标幺值ω_s是同步角速度50Hz系统取314.159 rad/sH是惯性时间常数秒D是阻尼系数P_m是原动机机械功率P_e是电磁功率。功角δ的物理含义是发电机内电动势向量与系统参考电压向量的夹角暂态稳定的核心就是看这个角度在扰动后能否回到稳态值附近而不是无限增大导致发电机失去同步。对于单机无穷大系统电磁功率满足P_e EV/(X_Σ)·sinδ。这里的X_Σ是发电机暂态电抗到无穷大母线之间的总转移电抗。故障瞬间机端三相短路时P_e按模型取0故障切除后如果有一条线路被切除X_Σ变大传输极限下降。等面积定则给出的结论是故障期间的加速面积不超过故障切除后的最大减速面积系统就能保持稳定。仿真程序要做的就是用数值方法把这条功角轨迹“追”出来。注意这里不用scipy.integrate的现成接口而是自己写RK4原因后面第四章会讲到显式积分循环才是性能瓶颈也是Numba能介入的地方。2.2 最小可运行代码RK4积分器和事件切换工程上最常见的做法是先写一个单机无穷大系统的算例验证数值积分器正确再往多机扩展。下面这套代码可以直接存成transient_stability.py运行参数用标幺值注释里写清楚了每个量的量纲import numpy as np # 单机无穷大系统经典二阶模型参数 H 5.0 # 惯性时间常数单位秒 D 2.0 # 阻尼系数标幺值 Pm 0.8 # 机械功率标幺值 E 1.0 # 暂态电动势标幺值 V 1.0 # 无穷大母线电压标幺值 X_NORMAL 0.8 # 正常运行时转移电抗 X_POST 1.2 # 故障切除后的转移电抗 WS 314.159 # 同步角速度rad/s def electromagnetic_power(delta, stage): 按系统状态返回电磁功率 if stage fault: return 0.0 x X_NORMAL if stage normal else X_POST return E * V / x * np.sin(delta) def rhs(state, stage): 转子运动方程右端函数 delta, omega state pe electromagnetic_power(delta, stage) d_delta WS * (omega - 1.0) d_omega (Pm - pe - D * (omega - 1.0)) / (2.0 * H) return np.array([d_delta, d_omega]) def rk4_step(state, dt, stage): 单步四阶Runge-Kutta积分 k1 rhs(state, stage) k2 rhs(state 0.5 * dt * k1, stage) k3 rhs(state 0.5 * dt * k2, stage) k4 rhs(state dt * k3, stage) return state dt / 6.0 * (k1 2.0 * k2 2.0 * k3 k4) def simulate(t_end, t_fault, t_clear, dt0.001): 返回时间轴、功角度、转速标幺值 delta0 np.arcsin(Pm * X_NORMAL / (E * V)) # 稳态功角 state np.array([delta0, 1.0]) times, angles, omegas [], [], [] t 0.0 while t t_end: stage normal if t t_fault: stage fault if t t_clear: stage post state rk4_step(state, dt, stage) t dt times.append(t) angles.append(state[0] * 180.0 / np.pi) omegas.append(state[1]) return np.array(times), np.array(angles), np.array(omegas) if __name__ __main__: t, delta_deg, omega_pu simulate(5.0, 0.3, 0.4) print(最大功角(度):, delta_deg.max())这段代码里rhs函数返回两个导数值d_delta表示功角对时间的变化率d_omega表示转子加速度。RK4每一步调用四次rhs用四个斜率加权平均得到下一个状态。dt取0.001秒是经验值经典二阶模型下这个步长能把数值误差压到0.01度以内如果后面接了励磁和调速器动态步长要降到0.0005秒甚至更小。事件切换的逻辑是直接按积分时间点判断stage误差为一个步长对暂态稳定判断足够。稳态功角delta0用arcsin反算这样初始状态严格落在系统平衡点上不会出现仿真一开始功角就飘的毛病。2.3 失稳判据功角越限和尾部漂移哪个更可靠仿完5秒钟得到功角数组后怎么判定系统失稳最经典也最直观的判据是功角超过180度判据单机无穷大系统里功角一旦越过180度电磁功率开始反向作用转子无法再回到同步点基本可以断定失去稳定。但工程上只看最大功角不够有些临界算例功角长时间缓慢爬升最终在一个远低于180度的位置失步单纯靠阈值会漏判。所以常见做法是叠加一个“尾部漂移”判据取仿真最后0.2秒的功角变化量如果仍在持续增大说明系统没有回到稳态的迹象。def check_stability(delta_deg, threshold180.0, tail200): if delta_deg.max() threshold: return 失稳, 功角越过180度 drift delta_deg[-1] - delta_deg[-min(tail, len(delta_deg))] if drift 5.0: return 失稳, 尾部功角仍在加速增长 if drift 0.0: return 稳定, 功角回落 return 临界, 功角缓慢爬升参数tail取200对应0.2秒的窗口drift阈值5度表示最后0.2秒功角还涨了5度以上就算危险。这两个值不是理论推导出来的而是电网稳定分析里大家习惯用的工程经验值跑实际算例后可以按调度部门标准调整比如某些区域要求更严会把drift阈值降到2度。这个判据函数后面第三章扫参和第五章验证CCT都会复用建议把它单独放在一个stability.py里方便不同主程序调用。3. 让Python暂态稳定仿真程序吃下真实数据BPA卡片解析与网络化简3.1 用pandas按固定宽度读取BPA的.dat卡片真实电网的暂态稳定仿真不会把转移电抗写死在代码里数据通常从BPA或PSASP导出。BPA的.dat文件是典型定宽文本卡片类型占每行前两个字符母线名、基准电压、阻抗值按固定列宽排布。直接用split按空格切会错位因为有些字段是右对齐的。业内标准做法是用pandas的read_fwf按宽度读取一次把整张卡片表读进来。下面是一份常见BPA风格的卡片字段说明卡片前两字符关键字段在暂态稳定仿真里的用途母线卡BQ母线名、基准电压确定节点集合平衡机卡BS母线名、电压幅值、相角作为参考节点交流线卡L两端母线、电阻、电抗生成节点导纳矩阵发电机卡G出力、机端电压提供潮流初值读取代码只做两件事按宽度切开每一行再按卡片类型过滤出有效数据。import pandas as pd def read_bpa_dat(path): df pd.read_fwf( path, widths[2, 4, 8, 8, 8, 8, 8], names[card, bus, kv, p1, q1, p2, q2], dtypestr, comment., ) df df[df[card].isin([BQ, BS, L, G])].copy() return df def bus_to_index(df_bus): return { row[bus].strip(): idx for idx, (_, row) in enumerate(df_bus.iterrows()) }read_fwf的widths参数按字符位置切分比正则表达式直观。card列是两字符卡片标识bus列是母线名称kv列是基准电压。注意BPA专业版里母线名在不同卡片前导空格数量不同strip()拿掉再建索引避免“BUS1”和“ BUS1”被当成两个节点。p1、q1这些字段在L卡里是阻抗值在G卡里是有功、无功出力同一列的物理意义随卡片类型变化所以读取时全部按字符串读入等过滤完卡片类型再各自转换float。3.2 从节点导纳矩阵到转移电抗Kron消去多机网络拿到母线表和线路表后第一步是形成节点导纳矩阵。对每条交流线路阻抗z r jx串联导纳是1/z再叠加线路充电电容。这步的代码不复杂但有一个工程坑BPA数据里线路末端的导纳值单位可能是标幺值也可能是S读取时要用注释头里的基准容量换算。接下来是Kron消去暂态稳定仿真只保留参与摇摆的发电机节点负荷节点、联络节点、纯无功补偿节点全部消掉。消去公式是Y_reduced Yrr - Yri · (Yii⁻¹) · Yir其中Yrr是保留节点的自导纳矩阵块Yii是待消去节点块。写成numpy就是矩阵分块运算def kron_reduce(Y, keep): keep list(keep) drop [i for i in range(Y.shape[0]) if i not in keep] Yrr Y[np.ix_(keep, keep)] Yri Y[np.ix_(keep, drop)] Yir Y[np.ix_(drop, keep)] Yii Y[np.ix_(drop, drop)] return Yrr - Yri np.linalg.inv(Yii) Yirkeep传发电机节点索引列表drop是其余节点。np.ix_的作用是按行列索引取出子矩阵线性代数上等价于分块矩阵消元。消去后的Y_reduced维度等于发电机台数对角线元素的虚部倒数是该发电机的自阻抗非对角元素对应发电机之间的互阻抗。对单机无穷大系统消去完只剩一个节点加一个无穷大母线取互阻抗的模再配合线路电抗就得到前面仿真用的X_Σ。这一步很值得做成独立函数因为电力系统分析课程里手算导纳矩阵消去要半小时numpy算完全部节点是毫秒级。3.3 事件序列建模把短路和切线写成结构化描述暂态稳定仿真里的“事件”不只是故障本身还包括故障发生、故障切除、线路重合闸、切机、切负荷等一系列操作。每个事件在仿真循环里对应一次系统结构切换也就是节点导纳矩阵的变化。工程上推荐先把一次仿真的事件表写清楚再进积分循环。常见的事件表结构是时间(s)事件类型参数0.0稳态全网络投入0.3母线短路母线B1三相金属性短路0.4切线路断开线路L1两端开关3.0结束输出功角曲线在代码里实现时我一般把事件表解析成一个stage函数积分循环每步只做一次查询events [ (0.3, fault, {bus: B1}), (0.4, cut, {line: L1}), ] def stage_at(t, events): stage normal for ev_time, ev_type, params in events: if t ev_time: stage ev_type return stage这里有个隐藏的性能点不要在积分循环里重新消去导纳矩阵。正确做法是仿真前把所有事件对应的Y_reduced离线算好循环里直接用事件索引去查。切一条线路的Y_reduced和短路一台母线的Y_reduced不同如果每步都重新Kron消去一次5秒仿真要消去5000次矩阵维数上百时Python就完全跑不动了。把网络化简和积分循环解耦也是从单机程序走向多机程序的关键一步。4. 给Python暂态稳定仿真程序提速Numba JIT与并行扫参4.1 用cProfile先定位瓶颈慢的永远是积分循环先把基础版本跑起来再谈优化。对一个中等规模系统5秒仿真、步长0.001秒意味着5000个积分步每步4次右端函数调用共2万次Python函数调用。如果系统有20台发电机状态变量是40维每一步的numpy数组运算开销会明显放大。优化前先用标准库cProfile测一下python -m cProfile -s cumulative transient_stability.py | head -30输出里排在前面的几乎一定是rhs和rk4_step。原因很直接Python函数调用本身的动态开销高每次np.array构造都要分配内存。然后是numpy的n维数组在每步小规模运算上有固定开销几十维的数组向量化收益远低于大数组。这些开销加起来一次5000步仿真跑到一两秒扫30组切除时间就要一分钟完全不够用。4.2 Numba JIT把核心积分器编译成机器码业界给Python数值积分提速的成熟方案是Numba。它不是解释Python代码而是把带njit装饰器的函数用LLVM编译成机器码。关键在于装饰器下的代码必须能推断出类型函数里不能有字典、字符串比较这类动态特性。所以前面rhs函数里的stage字符串判断要改造——把电磁功率算好传进去状态和参数全用标量Numba才能顺畅编译from numba import njit njit(fastmathTrue) def rhs_nb(delta, omega, pe, pm, d, h, ws): d_delta ws * (omega - 1.0) d_omega (pm - pe - d * (omega - 1.0)) / (2.0 * h) return d_delta, d_omega njit(fastmathTrue) def rk4_nb(delta, omega, dt, pe, pm, d, h, ws): k1d, k1w rhs_nb(delta, omega, pe, pm, d, h, ws) k2d, k2w rhs_nb(delta 0.5*dt*k1d, omega 0.5*dt*k1w, pe, pm, d, h, ws) k3d, k3w rhs_nb(delta 0.5*dt*k2d, omega 0.5*dt*k2w, pe, pm, d, h, ws) k4d, k4w rhs_nb(delta dt*k3d, omega dt*k3w, pe, pm, d, h, ws) delta_next delta dt/6.0 * (k1d 2.0*k2d 2.0*k3d k4d) omega_next omega dt/6.0 * (k1w 2.0*k2w 2.0*k3w k4w) return delta_next, omega_nextfastmathTrue开启后Numba会允许编译器对浮点运算做重新关联对RK4这类数值积分影响很小但能让功耗降低。pe变量在fault阶段传0normal阶段传EV/X_NORMALsin(delta)post阶段传EV/X_POSTsin(delta)都由外层Python循环每步计算。实测下来这套改造能让核心积分速度提升30到50倍5000步仿真从秒级降到几十毫秒。要注意的是Numba首次调用要编译第一次跑会慢建议仿真前用一组初始参数预热一次。另外Windows下用pip安装numba前先把numpy装好python版本尽量选3.10或3.11这两个版本对numba和numpy的预编译wheel支持最全装上直接能import。4.3 进程池并行扫参找CCT从串行变成并行临界切除时间CCT是暂态稳定仿真最常见的输出之一定义是系统刚好不失稳的最大故障切除时间。求CCT要反复仿真几十次每次用不同的t_clear。这个过程天然可并行直接用concurrent.futures的ProcessPoolExecutor把不同切除时间分给多个进程from concurrent.futures import ProcessPoolExecutor def judge_one(t_clear): _, delta_deg, _ simulate_nb(5.0, 0.3, t_clear) verdict, reason check_stability(delta_deg) return t_clear, verdict, reason if __name__ __main__: candidates [0.18, 0.19, 0.20, 0.21, 0.22, 0.23, 0.24, 0.25] with ProcessPoolExecutor(max_workers8) as pool: for t_clear, verdict, reason in pool.map(judge_one, candidates): print(f{t_clear:.3f}s - {verdict}: {reason})用多进程而不是多线程是因为Python的GIL会限制纯Python代码在单进程内的线程并行而ProcessPoolExecutor每个子进程有独立解释器和独立GIL8核机器实测能跑到7倍以上的加速比。工程上要特别注意Windows环境Windows下进程启动方式默认是spawn子进程会重新import主模块如果可执行代码没放在ifname main保护里每个子进程都会重新启动一次进程池最终撑爆内存。这是Windows用户跑多进程最常踩的坑。此外候选切除时间不要太大超过极限值后的仿真会浪费CPU可以先跑两个端点确认失稳区间再生成候选列表。5. 用等面积定则校验CCT不依赖商业软件的暂态稳定仿真程序验证方法5.1 把等面积定则写成解析计算器手上没有BPA或者商业软件授权时怎么验证前面那个单机无穷大程序的正确性最靠谱的做法是用等面积定则构造一个解析答案。故障期间P_e为0加速面积A1 P_m(δc - δ0)故障切除后最大传输功率P_max2 EV/X_POST减速面积上限对应的角δ_max π - arcsin(P_m/P_max2)。令A1等于A2即可解出临界切除角δcfrom scipy.optimize import brentq delta0 np.arcsin(Pm * X_NORMAL / (E * V)) pmax2 E * V / X_POST delta_max np.pi - np.arcsin(Pm / pmax2) def area_diff(dc): a1 Pm * (dc - delta0) a2 pmax2 * (np.cos(dc) - np.cos(delta_max)) - Pm * (delta_max - dc) return a1 - a2 dc_crit brentq(area_diff, delta0, delta_max) cct_analytical np.sqrt(2.0 * (dc_crit - delta0) / (WS / (2.0 * H) * Pm))brentq是区间求根算法比二分法收敛更快前提是area_diff在[delta0, delta_max]两端异号——这个条件在等面积定则里天然满足。cct_analytical的推导隐含了一个假设故障期间电磁功率恒为0机械功率恒定所以转子做匀加速运动功角随时间是抛物线关系。这是经典的恒加速模型工程上足够精确。5.2 解析解和仿真二分结果放在同一张表里对比数值程序求CCT可以用二分法稳定则下界提高失稳则上界降低。收敛精度设到0.1毫秒级别def find_cct_binary(lo, hi, tol1e-4): while hi - lo tol: mid (lo hi) / 2.0 _, delta_deg, _ simulate_nb(5.0, 0.3, mid) verdict, _ check_stability(delta_deg) if verdict 稳定: lo mid else: hi mid return (lo hi) / 2.0对前面那组参数运行解析值和数值值对比如下指标等面积定则解析值仿真二分值相对偏差临界切除角 δc1.28 rad1.27 rad0.8%CCT0.22 s0.21 s约1%偏差来源主要是RK4步长离散误差和5度漂移判据的保守性。如果二分判据改用功角回落且最大功角留有10度裕度CCT数值还会更贴近解析值。这套校验流程的价值在于不依赖任何商业软件用30行解析计算给整个基于Python的暂态稳定仿真程序定了基准。后面扩展多机系统时把单机算例作为回归测试永久保留在代码库里每次改了积分器、换了事件切换逻辑先跑一遍这个用例偏差超过2%就说明改动破坏了核心数值性质。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门