ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

风电潮流计算实战:从WIND1.zip数据解析到N-1安全分析

风电潮流计算实战:从WIND1.zip数据解析到N-1安全分析 简介面向电力系统工程师与科研人员这份风电潮流计算与安全分析资源包以 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, zonebranch.csv集电线路和主变支路fb, tb, r_pu, x_pu, b_pu, s_mva, type, length_km, vh_kv, vl_kv, vkr, vkturbine.csv风电机组并网点bus_id, p_mw, q_mvar, v_set_pu, q_max_mvarREADME.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, encodinggbk) with zf.open(branch.csv) as f: branches pd.read_csv(f, encodinggbk) with zf.open(turbine.csv) as f: turbines pd.read_csv(f, encodinggbk) # 如果原始表头不是英文先 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 再变回 PQ3.1 风机稳态模型PQ 和 PV 的适用边界双馈异步风机和直驱风机在机电稳态里通常都可以简化成可控功率源。如果机端变流器不做电压控制就按恒功率因数运行用 PQ 节点建模如果参与电压调节就按设定电压运行用 PV 节点建模。但风电场的 PV 节点不能像火电机组那样无限调节无功输出有上限这也是安全分析时最容易出问题的地方。风机控制方式稳态节点有功给定无功给定注意事项恒功率因数PQP 由风速换算QP×tan(phi)功率因数通常 0.95 滞后恒电压PVP 由风速换算自动调节需要 Qmax/Qmin恒无功PQP 由风速换算Q 常数不适合并网点电压变化大的场景选择模型时先看 WIND1.zip 的 turbine.csv 里有没有v_set_pu和q_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_mva100.0) # 创建母线并保留原始 bus_id 到 pandapower 索引的映射 bus_map {} for _, b in buses.iterrows(): idx pp.create_bus(net, vn_kvb[vn_kv], nameb[name]) bus_map[b[bus_id]] idx # 外部电网并网点母线 id 从 README 读取这里假设为 1 grid_bus bus_map[1] pp.create_ext_grid(net, busgrid_bus, vm_pu1.0, va_degree0.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_busfb, to_bustb, sn_mvabr[s_mva], vn_hv_kvbr[vh_kv], vn_lv_kvbr[vl_kv], vkr_percentbr[vkr], vk_percentbr[vk], pfe_kw0, i0_percent0 ) 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_busfb, to_bustb, length_kmlength_km, r_ohm_per_kmbr[r_pu] * base_z / length_km, x_ohm_per_kmbr[x_pu] * base_z / length_km, c_nf_per_km0.0, max_i_kafloat(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_mva100.0就没错如果基准容量不同要同步修改网络、线路和变压器的托盘否则算出来的电压和潮流全是错的。3.3 接入风机并设置 PQ/PV 参数风机接入用create_sgen它代表分布式电源/储能比gen更适合电压非恒定的新能源节点。初始按 PQ 模式接入for _, t in turbines.iterrows(): pp.create_sgen( net, busbus_map[t[bus_id]], p_mwt[p_mw], q_mvart[q_mvar] if not pd.isna(t[q_mvar]) else 0.0, namefWT_{t[bus_id]} )这里q_mvar如果是 NaN说明该风机按单位功率因数运行填 0 即可。如果多个风机在同一母线p_mw 累计会超过变压器容量这一步正好暴露 WIND1.zip 中的容量配置错误。要模拟电压控制改用带可控标记的sgenfor _, t in turbines.iterrows(): pp.create_sgen( net, busbus_map[t[bus_id]], p_mwt[p_mw], q_mvar0.0, controllableTrue, vm_pu_setpointt[v_set_pu], min_q_mvar-t[q_max_mvar], max_q_mvart[q_max_mvar] )3.4 跑潮流并检查收敛结果模型建好后用runpp跑牛顿-拉夫逊算法pp.runpp(net, algorithmnr, tolerance_mva1e-8) print(net.res_bus[[vm_pu, va_degree, p_mw, q_mvar]])如果runpp报不收敛最可能的原因是支路参数单位没对、变压器变比错误或者母线 ID 不连续。先用net.line和net.trafo检查创建了多少元件再对比 WIND1.zip 中的拓扑表。收敛后也不要急着看结果先看外部电网的注入功率是否和风电场总出力在同量级。3.5 多出力场景下的风电潮流计算风电场不会总在额定风速安全评估要看多个出力点。最简单的方法是用scaling缩放所有sgenfor load_factor in [1.0, 0.75, 0.5, 0.25, 0.0]: net.sgen[scaling] load_factor pp.runpp(net, algorithmnr, report_errorsTrue) 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_connectivityFalse) 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 resultscheck_connectivityFalse允许网络开断后出现孤立岛否则 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_percent和res_trafo.loading_percent它们已经按载流量折算。如果某个开断场景下主变负载率到 90%可能不是变压器容量不够而是开断后潮流集中到了单台主变上这时优先看能否从拓扑上分流。4.4 风机跳闸场景怎么加进故障集除了支路开断风电场安全分析还要看风机连锁脱网。WIND1.zip 里的 turbine.csv 通常把每台风机映射到某条母线跳一台风机就等于把该台sgen退出运行。实现方法类似遍历所有sgenfor 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, busbus_map[t[bus_id]], p_mwt[p_mw], q_mvar0.0, controllableTrue, vm_pu_setpoint1.0, min_q_mvar-q_max, max_q_mvarq_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 中某条线路或变压器参数没有被正确建模回去查支路表比在结果上猜原因有效得多。本文还有配套的精品资源点击获取
返回列表