风电潮流计算实战:从WIND1.zip数据解析到N-1安全分析
2026/9/15 2:59:31 网站建设 项目流程

简介:面向电力系统工程师与科研人员,这份风电潮流计算与安全分析资源包以 IEEE 14 节点系统为实例,解决风电并网后系统电压、电流、功率分布计算及稳定性评估问题,特别适合已掌握 MATLAB 基础、希望深入电力系统仿真的人群。压缩包共 2 个文件,包含一个 MATLAB 脚本文件和一个数据文件,整体仅 5KB。脚本文件用于搭建潮流计算流程,可调整风电节点位置与注入功率,模拟不同并网场景;数据文件则保存模型参数、计算结果或仿真数据,便于进行静态安全分析和结果复现。已有 161 人学习该资源。通过实际操作,用户可以掌握风电潮流计算的方法流程,评估风电出力变化对电网电压、频率、功角稳定性的影响,识别潜在风险,为风电场规划、设计和运行控制提供量化依据,助力清洁能源安全高效并网。

1. WIND1.zip 让风电潮流计算卡在了第一步

WIND1.zip 不是标准潮流格式,而是一个关于 wind farm 的数据集合包。很多团队拿到手后先解压,然后面对一堆 CSV、JSON 和 Readme 发愣:哪些字段是母线、哪些是支路,风机是恒速异步机还是双馈变速机,根本没有统一契约。我做并网评估时,第一步永远是确认数据契约,而不是立刻套用潮流算法。

“风 潮流”和“风电 潮流”在工程里是同一件事:风电场出力波动,整个集电系统的有功和无功分布随之改变,我们关心的是多个出力水平下母线电压、支路负载和网损。WIND1.zip 这类数据包把风电场拓扑、机组参数和边界条件打包在一起,让你能针对不同风速场景反复计算。我会沿着“解析数据包 → 建立潮流模型 → 计算潮流 → N-1 安全分析”的顺序,把每一步可执行的代码、参数和坑都展开,适合要做风电并网评估的工程师,也适合需要把理论落到代码上的研究生。

2. 从 WIND1.zip 到数据模型:风电场潮流数据包的结构化解析

WIND1.zip 内部文件命名每个项目都不同,但结构有很强共性:至少会有描述母线、交流线路、变压器和风电机组的表,以及一个说明单位口径的文档。解包前先确认这些文件的列名和单位,能省掉后面好几个小时的对账时间。

2.1 先别解压,用 5 行代码看清 WIND1.zip 内部结构

常见做法不是直接双击解压,而是先把压缩包当成独立存储介质来检视。Python 的 zipfile 模块可以列出所有文件名称和原始大小,这一步能快速判断里面是纯 CSV、JSON 还是某仿真软件的导出文件,也能发现是不是把多套方案挤在同一个压缩包里。

import zipfile from pathlib import Path zip_path = Path("WIND1.zip") with zipfile.ZipFile(zip_path) as zf: for info in zf.infolist(): print(f"{info.filename}\t{info.file_size}")

上面的代码不做任何假设,只用infolist()输出 entry 的相对路径和大小。如果看到文件名带目录层级,说明压缩包内部还有子文件夹,pandas 读取时要拼上相应前缀;如果文件名带中文或空格,读取后最好统一重命名成英文字段,否则后面 pandapower 的接口会报编码相关错误。

典型的 WIND1.zip 内容大致如下:

文件内容关键字段
bus.csv风电场各电压等级母线bus_id, vn_kv, name, zone
branch.csv集电线路和主变支路fb, tb, r_pu, x_pu, b_pu, s_mva, type, length_km, vh_kv, vl_kv, vkr, vk
turbine.csv风电机组并网点bus_id, p_mw, q_mvar, v_set_pu, q_max_mvar
README.txt单位与基准说明base_mva, pf_rule

拿到文件清单后,我一般会先看有没有 README 或 units.json,里面往往写着基准容量是 100 MVA 还是 1 MVA,这在后面换算标幺时是致命的。如果压缩包内还有shap.dat之类的文件,先不要丢弃,它可能记录了节点连接顺序,能用来校核支路表是否完整。

2.2 用 pandas 把 zip 中的 CSV 读成 DataFrame

确认字段后,就可以直接从压缩包里读取 CSV,不必把解压出来的文件落到磁盘上。zipfile 的open方法返回二进制流,pandas 可以直接消费,这样临时目录里不会散落一堆文件。

import pandas as pd import zipfile with zipfile.ZipFile("WIND1.zip") as zf: with zf.open("bus.csv") as f: buses = pd.read_csv(f, encoding="gbk") with zf.open("branch.csv") as f: branches = pd.read_csv(f, encoding="gbk") with zf.open("turbine.csv") as f: turbines = pd.read_csv(f, encoding="gbk") # 如果原始表头不是英文,先 rename 成统一列名 buses = buses.rename(columns={"母线号": "bus_id", "额定电压kV": "vn_kv"}) branches = branches.rename(columns={ "首端节点": "fb", "末端节点": "tb", "r标幺": "r_pu", "x标幺": "x_pu", "b标幺": "b_pu", "容量MVA": "s_mva", "类型": "type", "长度km": "length_km" }) turbines = turbines.rename(columns={ "接入母线": "bus_id", "有功MW": "p_mw", "无功Mvar": "q_mvar", "电压设定": "v_set_pu" })

这里的encoding参数是第一个容易踩的坑。很多 WIND1.zip 的历史数据来自国内风电场规划软件,CSV 是从 Excel 另存过来的,用gbk编码才能读通;读出来是乱码时,先试utf-8-sig,再试gbk,不要盲改。列名重命名是为了让后续代码不依赖原始表头大小写,也方便直接映射到 pandapower 的参数。

2.3 WIND1.zip 里最容易被忽略的单位和极性陷阱

读入不代表干净,最花时间的往往不是结构,而是单位。风电场集电线路可能同时出现“长度×单位阻抗”和“已折算标幺值”两种风格,WIND1.zip 中我遇到过把线路电阻标成 30℃ 条件下的,也遇到过把电纳单位从 μS/km 混写成 S/km 的情况。处理前先找一个已知母线电压和短路容量来校验,比盲目信任参数表更可靠。

另一个常见问题是变压器极性。如果是三绕组变压器,branch 表可能只给了高压侧到中压侧的参数,而低压侧绕组在另一个文件里。安全分析时一旦把方向接反,无功潮流和电压会同时异常。所以数据解析完成后,先打印 bus 表的前几行和 branch 表的前几行,确认连接关系是放射状还是闭环,再进入模型构建。如果条件允许,还可以把读进来的支路表用 networkx 画一张拓扑图,节点用 bus_id,边用 fb/tb。一张放射状图能快速发现数据里常见的重复节点编号或孤立节点,这两种问题在真实数据里很常见,而且单看表格很难定位。

3. 风电潮流计算:把风机出力节点从 PQ 变成 PV 再变回 PQ

3.1 风机稳态模型:PQ 和 PV 的适用边界

双馈异步风机和直驱风机在机电稳态里通常都可以简化成可控功率源。如果机端变流器不做电压控制,就按恒功率因数运行,用 PQ 节点建模;如果参与电压调节,就按设定电压运行,用 PV 节点建模。但风电场的 PV 节点不能像火电机组那样无限调节,无功输出有上限,这也是安全分析时最容易出问题的地方。

风机控制方式稳态节点有功给定无功给定注意事项
恒功率因数PQP 由风速换算Q=P×tan(phi)功率因数通常 0.95 滞后
恒电压PVP 由风速换算自动调节需要 Qmax/Qmin
恒无功PQP 由风速换算Q 常数不适合并网点电压变化大的场景

选择模型时先看 WIND1.zip 的 turbine.csv 里有没有v_set_puq_max_mvar字段。有v_set_pu但没有无功上限,大概率是设计幕帘数据,不能直接当 PV 用;否则计算时会导致每次无功越限被截断,结果和实际运行不符。

3.2 用 pandapower 建最小风电场潮流模型

在 Python 生态里,pandapower 的create_line_from_parameters可以接受整条支路的标幺值并折成每公里参数;变压器则用create_transformer_from_parameters,把 WIND1.zip 中的短路电压百分比录进去。下面的代码先重建 bus_id 映射,因为原始 ID 不一定是连续整数。

import pandapower as pp net = pp.create_empty_network(sn_mva=100.0) # 创建母线,并保留原始 bus_id 到 pandapower 索引的映射 bus_map = {} for _, b in buses.iterrows(): idx = pp.create_bus(net, vn_kv=b["vn_kv"], name=b["name"]) bus_map[b["bus_id"]] = idx # 外部电网:并网点母线 id 从 README 读取,这里假设为 1 grid_bus = bus_map[1] pp.create_ext_grid(net, bus=grid_bus, vm_pu=1.0, va_degree=0.0) # 从 bus 表得到每个节点的额定电压,用来换算标幺阻抗 bus_vn = {b["bus_id"]: b["vn_kv"] for _, b in buses.iterrows()} for _, br in branches.iterrows(): fb = bus_map[br["fb"]] tb = bus_map[br["tb"]] if br["type"].startswith("trafo"): pp.create_transformer_from_parameters( net, from_bus=fb, to_bus=tb, sn_mva=br["s_mva"], vn_hv_kv=br["vh_kv"], vn_lv_kv=br["vl_kv"], vkr_percent=br["vkr"], vk_percent=br["vk"], pfe_kw=0, i0_percent=0 ) else: vn_kv = bus_vn[br["fb"]] base_z = vn_kv**2 / net.sn_mva length_km = float(br.get("length_km", 1.0)) pp.create_line_from_parameters( net, from_bus=fb, to_bus=tb, length_km=length_km, r_ohm_per_km=br["r_pu"] * base_z / length_km, x_ohm_per_km=br["x_pu"] * base_z / length_km, c_nf_per_km=0.0, max_i_ka=float(br.get("max_i_ka", 1.0)) )

这里的关键是基准容量。create_transformer_from_parameters里的sn_mva是变压器额定容量,而线路标幺值换算时使用的base_z来自全网基准容量net.sn_mva。如果 WIND1.zip 的 README 里写的是 100 MVA,那么net.sn_mva=100.0就没错;如果基准容量不同,要同步修改网络、线路和变压器的托盘,否则算出来的电压和潮流全是错的。

3.3 接入风机并设置 PQ/PV 参数

风机接入用create_sgen,它代表分布式电源/储能,比gen更适合电压非恒定的新能源节点。初始按 PQ 模式接入:

for _, t in turbines.iterrows(): pp.create_sgen( net, bus=bus_map[t["bus_id"]], p_mw=t["p_mw"], q_mvar=t["q_mvar"] if not pd.isna(t["q_mvar"]) else 0.0, name=f"WT_{t['bus_id']}" )

这里q_mvar如果是 NaN,说明该风机按单位功率因数运行,填 0 即可。如果多个风机在同一母线,p_mw 累计会超过变压器容量,这一步正好暴露 WIND1.zip 中的容量配置错误。

要模拟电压控制,改用带可控标记的sgen

for _, t in turbines.iterrows(): pp.create_sgen( net, bus=bus_map[t["bus_id"]], p_mw=t["p_mw"], q_mvar=0.0, controllable=True, vm_pu_setpoint=t["v_set_pu"], min_q_mvar=-t["q_max_mvar"], max_q_mvar=t["q_max_mvar"] )

3.4 跑潮流并检查收敛结果

模型建好后,用runpp跑牛顿-拉夫逊算法:

pp.runpp(net, algorithm="nr", tolerance_mva=1e-8) print(net.res_bus[["vm_pu", "va_degree", "p_mw", "q_mvar"]])

如果runpp报不收敛,最可能的原因是支路参数单位没对、变压器变比错误,或者母线 ID 不连续。先用net.linenet.trafo检查创建了多少元件,再对比 WIND1.zip 中的拓扑表。收敛后也不要急着看结果,先看外部电网的注入功率是否和风电场总出力在同量级。

3.5 多出力场景下的风电潮流计算

风电场不会总在额定风速,安全评估要看多个出力点。最简单的方法是用scaling缩放所有sgen

for load_factor in [1.0, 0.75, 0.5, 0.25, 0.0]: net.sgen["scaling"] = load_factor pp.runpp(net, algorithm="nr", report_errors=True) print(f"出力系数={load_factor:.2f}, Vmin={net.res_bus.vm_pu.min():.3f}, " f"最大负载率={net.res_line.loading_percent.max():.1f}%")

注意scaling是乘数,不是绝对出力;如果 WIND1.zip 中某台风机 p_mw 已经是 0.5 MW,乘 0.5 就变成 0.25 MW。典型风电场出力曲线通常取额定、80%、50%、20%、0% 五个点,覆盖从大风到小风的安全边界。

4. 风电场安全分析:N-1 支路开断、电压越限与风机跳闸

4.1 安全分析为什么要跑故障集而不是单点潮流

单一断面潮流只能说明某个运行点没过载,不能说明在任一元件检修或故障退出后仍然安全。风电场内部集电线路、主变、并网线都是 N-1 的对象,不合理的拓扑甚至可能出现“断一条 35kV 线路,另一条线路立刻过载”的情况。所以安全分析的第一步是定义故障集,WIND1.zip 里有多少条支路,就至少跑多少种开断方式。

4.2 用循环实现 N-1 支路扫掠

在 pandapower 中,N-1 的典型做法是逐条把支路in_service设为 False,再跑潮流,记录电压和负载率。注意每次计算后必须恢复,否则上一次的开断状态会污染下一轮。先写一个取最大负载率的辅助函数:

def max_loading(net): val = 0.0 for res in [net.res_line, net.res_trafo]: if not res.empty and "loading_percent" in res: val = max(val, res["loading_percent"].max()) return val

然后遍历线路和变压器:

def n1_scan(net): results = [] for table, comp in [(net.line, "line"), (net.trafo, "trafo")]: for idx in table.index: table.loc[idx, "in_service"] = False try: pp.runpp(net, check_connectivity=False) vmin = net.res_bus.vm_pu.min() loading = max_loading(net) results.append((comp, idx, vmin, loading)) except pp.LoadflowNotConverged: results.append((comp, idx, None, None)) finally: table.loc[idx, "in_service"] = True return results

check_connectivity=False允许网络开断后出现孤立岛,否则 pandapower 会直接报“网络不连通”,无法计算孤岛电压。但也要注意,如果孤立岛中没有平衡节点,结果可能没有物理意义。所以在实际项目中,我会在finally里恢复支路,并对返回 None 的支路重点排查,而不是简单当作不收敛。

4.3 安全分析判据表

安全分析结果需要和并网导则比对,不能只看是否收敛。下表是我常用的判据:

指标合格范围超出后动作
母线电压0.95~1.05 pu投切无功补偿或调整分接头
并网点电压0.97~1.03 pu调整风机无功设定
集电线路负载率<100%限制风机出力或改拓扑
主变负载率<80%调整检修计划
网损无明显突变检查是否漏建支路

电压以 pu 为单位,和 WIND1.zip 中的 vn_kv 配合才能换算成实际电压;负载率则读res_line.loading_percentres_trafo.loading_percent,它们已经按载流量折算。如果某个开断场景下主变负载率到 90%,可能不是变压器容量不够,而是开断后潮流集中到了单台主变上,这时优先看能否从拓扑上分流。

4.4 风机跳闸场景怎么加进故障集

除了支路开断,风电场安全分析还要看风机连锁脱网。WIND1.zip 里的 turbine.csv 通常把每台风机映射到某条母线,跳一台风机就等于把该台sgen退出运行。实现方法类似,遍历所有sgen

for sgen_idx in net.sgen.index: pp.runpp(net) # 先恢复初始运行点 net.sgen.loc[sgen_idx, "in_service"] = False try: pp.runpp(net) vmin = net.res_bus.vm_pu.min() print(f"切机 {sgen_idx}: Vmin={vmin:.3f} pu") finally: net.sgen.loc[sgen_idx, "in_service"] = True

注意,风机跳闸后有功和无功同时消失,对电压的冲击比支路开断更直接。如果跳一台 5 MW 风机就导致母线电压低于 0.95 pu,说明该并网点的无功补偿容量不足,需要在 WIND1.zip 的原始数据上补加 SVC 或 SVG 模型,再重新计算。

5. 把 WIND1.zip 的风电潮流计算跑得又快又稳:三个落地技巧

5.1 解析结果缓存成 pickle,避免每次重新读 zip

风电项目通常要跑几十个潮流场景,反复解压读取会把大量时间浪费在 IO 上。我一般把解析好的 DataFrame 存成 pickle,脚本重跑时直接命中缓存。

import pickle from pathlib import Path cache = Path("wind1_cache.pkl") if cache.exists(): with open(cache, "rb") as f: buses, branches, turbines = pickle.load(f) else: # 这里放第 2 章的解包读取代码 with zipfile.ZipFile("WIND1.zip") as zf: # ... pass with open(cache, "wb") as f: pickle.dump((buses, branches, turbines), f)

缓存文件要放在和 WIND1.zip 同目录下,版本变化时手动删除一次即可。不要用 pickle 缓存跨 Python 版本迁移,换解释器环境后宁可重新解析,也不要加载旧对象。

5.2 用功率因数下限设定风机无功边界

WIND1.zip 中经常只有额定有功和额定容量,没有给出每个出力点的无功上限。常见做法是锁定功率因数不低于 0.95,这样q_max就变成p_mw * tan(acos(pf)),而不是一个固定常数。

import math for _, t in turbines.iterrows(): pf = 0.95 q_max = t.p_mw * math.tan(math.acos(pf)) pp.create_sgen( net, bus=bus_map[t["bus_id"]], p_mw=t["p_mw"], q_mvar=0.0, controllable=True, vm_pu_setpoint=1.0, min_q_mvar=-q_max, max_q_mvar=q_max )

这样当风机降出力时,无功能力也跟着缩水,更接近实际逆变器限流特性。如果 WIND1.zip 自带q_max_mvar,优先使用自带值,功率因数推算是备选方案。

5.3 潮流收敛后用功率平衡校验数据

收敛不是终点,结果可能因为少建一条负荷或母线 ID 错位而整体偏移。我每次跑完潮流都会做一个功率平衡检查:

p_gen = net.res_sgen.p_mw.sum() p_ext = net.res_ext_grid.p_mw.sum() p_loss = net.res_line.pl_mw.sum() + net.res_trafo.pl_mw.sum() diff = abs(p_gen + p_ext - p_loss) if diff > 1e-6: print(f"功率不平衡: {diff:.3f} MW")

这里p_ext可能是负值,表示外部电网吸收风电场功率;pl_mw是元件损耗,二者加总后应当等于风机注入。差值只要不是浮点噪声,就说明 WIND1.zip 中某条线路或变压器参数没有被正确建模,回去查支路表比在结果上猜原因有效得多。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询