简介:这份资源面向学习天然气管网水力计算与MATLAB编程的学生及工程技术人员,围绕气网静态模拟这一核心问题,提供从理论到实践的完整参考。压缩包共5个文件,约3.51MB,包含2张png拓扑图与误差分析图、1份pdf研究文档、1份xlsx基本参数表以及1个带详细备注的m程序文件,分别对应管网结构展示、参数输入、算法实现与结果验证等环节。资源以解节点方程法为主线,将管网划分为节点与边,通过求解节点压力与流量方程获得网络稳定状态,并借助实际运行数据校准模型,相对误差仅0.003,精度较高。读者可据此理解气网水力计算的建模思路,掌握MATLAB脚本的编写与调试方法,并对照参数表与拓扑图复现模拟流程、开展误差分析。目前已有308人学习下载,适合作为课程设计、毕业设计或工程入门阶段的实操素材。
1. 天然气管网静态模拟:一个压缩包背后到底藏着什么
拿到「天然气管网静态模拟.rar」这个标题的人,大概率正卡在同一个坎上:手里有管网拓扑、有气源参数、有各分输点的用气曲线,但就是算不出稳态工况下每一段管线的压力、流量和温度分布。静态模拟要解决的就是这件事——把管网在某一时刻的运行状态「拍一张照片」,用一套非线性方程组把节点压力、管段流量、压缩机工况全部解出来。它不关心从上一时刻怎么过渡过来,只关心这一时刻是否守恒、是否越限。
适合谁看?做城市燃气调度的、做长输管道设计复核的、做储气库注采方案比选的,以及被安排「先把模型跑通再说」的在校研究生。这个方向值不值得投入?如果你需要反复回答「某分输点加量后末端压力够不够」「某管段换口径后全线怎么变」这类问题,静态模拟是绕不开的基本功,而且一次搭好可以长期复用。
2. 静态模拟的物理骨架:从管段方程到节点平衡
2.1 管段压降方程到底用哪个
天然气管网的静态模拟,核心是一组描述「压力—流量」关系的代数方程。最常用的三个层次:
第一层是达西-魏斯巴赫(Darcy-Weisbach)通用形式,摩擦系数用科尔布鲁克(Colebrook)隐式公式迭代,精度最高但计算量大。第二层是专门针对天然气长输管道的威莫斯(Weymouth)公式,它假设摩擦系数只与管径有关,形式简单:
P1² - P2² = C · (T·Z·L / D^5) · Q²其中 P1、P2 是管段起终点压力(绝压),T 是气体温度,Z 是压缩因子,L 是管长,D 是内径,Q 是标准状态下流量,C 是单位换算系数。第三层是潘汉德尔(Panhandle)A/B 式,引入了雷诺数修正,适合大口径、高雷诺数工况。
我一般会这样选:城市中压管网(0.4 MPa 以下)用达西-魏斯巴赫配科尔布鲁克,因为流速低、摩擦占主导;长输干线(4 MPa 以上)先用威莫斯快速估算,再用潘汉德尔 B 式复核。压缩因子 Z 不能拍脑袋取 1,用 Standing-Katz 图拟合的 DAK 方程或 AGA8 方程算,否则高压段误差能到 5% 以上。
2.2 节点流量平衡与环网求解
管段方程写完后,每个节点要满足基尔霍夫流量守恒:流入该节点的流量之和等于流出之和加上该节点的用气量。对于树状管网,可以直接从气源点顺推;对于环状管网,必须联立求解。
常见做法是牛顿-拉夫逊法:把每个节点的压力作为未知量,管段流量用压差表示后代入节点方程,得到一组关于节点压力的非线性方程组。雅可比矩阵的稀疏结构直接对应管网拓扑,用稀疏矩阵求解器(如 scipy.sparse.linalg.spsolve)能大幅提速。
import numpy as np from scipy.sparse import lil_matrix from scipy.sparse.linalg import spsolve def build_jacobian(nodes, pipes, pressures): """ nodes: 节点列表,每个节点有 id 和用气量 pipes: 管段列表,每段有 from_node, to_node, D, L, T, Z pressures: 当前迭代的节点压力向量 返回:雅可比矩阵 J 和残差向量 F """ n = len(nodes) J = lil_matrix((n, n)) F = np.zeros(n) # 对每个管段,计算流量及其对两端压力的偏导 for pipe in pipes: i = pipe['from_node'] j = pipe['to_node'] dp2 = pressures[i]**2 - pressures[j]**2 # 威莫斯公式反算流量 Q = np.sign(dp2) * np.sqrt(abs(dp2) / pipe['C']) # 流量对压力的偏导 dQ_dPi = 0.5 * Q / dp2 * 2 * pressures[i] if dp2 != 0 else 0 dQ_dPj = -0.5 * Q / dp2 * 2 * pressures[j] if dp2 != 0 else 0 # 节点方程:流入 - 流出 - 用气 = 0 F[i] -= Q F[j] += Q J[i, i] += dQ_dPi J[i, j] += dQ_dPj J[j, i] -= dQ_dPi J[j, j] -= dQ_dPj # 减去节点用气量 for idx, node in enumerate(nodes): F[idx] -= node['demand'] return J.tocsr(), F这段代码的关键点:C是威莫斯公式里所有常数和管段几何参数的合并系数,需要根据单位制统一换算;np.sign(dp2)保证流量方向正确;雅可比矩阵用lil_matrix逐项填充再转csr求解,比稠密矩阵快一个数量级。迭代收敛判据一般取节点流量残差的最大绝对值小于 1e-6(标准立方米/秒量级)。
2.3 压缩机与调压器的处理
管网里不可能只有管段。压缩机站要指定出口压力或压比,调压器要指定下游压力。这些「定压节点」在方程组里表现为:该节点压力已知,不参与迭代,但它的流量作为未知量进入相邻管段的方程。实现时把节点分为「定压节点」和「定流量节点」两类,雅可比矩阵只对未知压力节点构建,已知压力节点的贡献移到残差向量右边。
提示:压缩机如果指定压比而非出口压力,需要把压比作为约束加入,此时该节点的压力仍未知,但多一个方程。常见做法是把压缩机出口节点拆成两个节点,中间用压比方程连接。
3. 从零搭建一个可复现的静态模拟流程
3.1 数据准备:管网拓扑怎么描述
一个可复现的静态模拟,输入数据必须包含四张表:节点表、管段表、气源表、用气表。我习惯用 CSV 或 Excel 管理,字段如下:
| 表名 | 关键字段 | 说明 |
|---|---|---|
| nodes | node_id, pressure_set, demand | 定压节点填 pressure_set,定流量节点填 demand |
| pipes | pipe_id, from_node, to_node, D_mm, L_km, T_K, Z | D 用内径,L 用公里,T 用开尔文 |
| sources | node_id, flow, pressure | 气源点,至少指定流量或压力之一 |
| demands | node_id, flow | 各分输点用气量,单位统一为万方/日或 m³/s |
单位制必须统一。我踩过的坑:管径用毫米、管长用公里、流量用万方/日,结果威莫斯系数算错,压力分布完全离谱。建议全部换算到国际单位制再代入公式,最后输出时再换回工程单位。
3.2 求解器搭建:牛顿-拉夫逊迭代的完整实现
有了雅可比矩阵,迭代框架就简单了:
def solve_static(nodes, pipes, max_iter=50, tol=1e-6): """ 牛顿-拉夫逊法求解静态管网 返回收敛后的节点压力向量 """ n = len(nodes) # 初始化压力:定压节点用设定值,其余用平均值 p = np.array([node.get('pressure_set', 1.0) for node in nodes]) for iteration in range(max_iter): J, F = build_jacobian(nodes, pipes, p) # 定压节点不参与求解 free_idx = [i for i, node in enumerate(nodes) if 'pressure_set' not in node] if not free_idx: break J_free = J[free_idx][:, free_idx] F_free = F[free_idx] delta = spsolve(J_free, -F_free) p[free_idx] += delta if np.max(np.abs(delta)) < tol: print(f"收敛于第 {iteration+1} 次迭代") return p raise RuntimeError("迭代未收敛,检查初值和管段参数")逻辑说明:free_idx筛选出压力未知的节点,雅可比矩阵只取这些行列。spsolve解线性方程组得到压力修正量,累加到当前压力。收敛判据用压力修正量的最大绝对值,比流量残差更直观。如果 50 次迭代还不收敛,八成是初值太离谱或者某段管径填错导致雅可比奇异。
参数怎么改:max_iter一般 30 就够,tol取 1e-6 对应压力精度约 0.001 MPa。如果管网很大(节点数超过 500),把spsolve换成scipy.sparse.linalg.splu做 LU 分解,每次迭代只回代,能快 3 到 5 倍。
3.3 结果验证:三个必须检查的物理量
算完之后不能直接信。我一般会检查三件事:
第一,节点流量平衡残差。把所有管段流量按方向汇总到每个节点,加上用气量,看是否接近零。残差大于 1e-4 就说明没收敛好。
第二,管段流速是否在合理范围。天然气长输管道经济流速一般 5~15 m/s,城市中压管网 3~8 m/s。如果某段算出 30 m/s,要么管径填小了,要么流量单位错了。
第三,压力分布是否单调。从气源到末端,压力应该总体递减(压缩机升压段除外)。如果出现末端压力高于起点,检查管段方向是否定义反了。
def check_results(nodes, pipes, pressures): """输出三个关键校验指标""" # 流量平衡残差 balance = {node['node_id']: 0.0 for node in nodes} for pipe in pipes: i, j = pipe['from_node'], pipe['to_node'] dp2 = pressures[i]**2 - pressures[j]**2 Q = np.sign(dp2) * np.sqrt(abs(dp2) / pipe['C']) balance[i] -= Q balance[j] += Q for node in nodes: balance[node['node_id']] -= node.get('demand', 0) max_residual = max(abs(v) for v in balance.values()) print(f"最大节点流量残差: {max_residual:.2e}") # 流速检查 for pipe in pipes: dp2 = pressures[pipe['from_node']]**2 - pressures[pipe['to_node']]**2 Q = np.sign(dp2) * np.sqrt(abs(dp2) / pipe['C']) area = np.pi * (pipe['D_mm']/1000/2)**2 v = abs(Q) / area if v > 20: print(f"警告: 管段 {pipe['pipe_id']} 流速 {v:.1f} m/s 偏高")4. 避坑与排查:静态模拟里最容易翻车的五个地方
4.1 现象:迭代震荡不收敛,残差在几个值之间跳
原因:初值给得太随意,或者管网里存在「死区」——某段管径极小、流量趋近于零,导致雅可比矩阵接近奇异。另一个常见原因是压缩因子 Z 取了常数,但高压段 Z 随压力变化明显,方程本身不自洽。
解决:初值用「气源压力向末端线性递减」生成,比统一给 1.0 强得多。对死区管段,加一个最小流量阈值(如 1e-6),避免除零。Z 用 DAK 方程每次迭代更新,收敛会慢一点但稳定。
4.2 现象:结果里某节点压力为负
原因:要么是管段方向定义反了,要么是该节点用气量超过了上游供气能力,物理上无解。还有一种可能是单位换算错误,比如把万方/日当成 m³/s 代入,流量大了三个数量级。
解决:先检查管段 from_node 和 to_node 是否与拓扑图一致。再用总气源量减去总用气量,如果差值为负,说明供需不平衡,需要调整气源或减少用气。单位统一用 m³/s 和 Pa 计算,输出再换。
4.3 现象:收敛了但压力分布明显不合理,末端压力比起点还高
原因:环网里出现了「反向流」,但代码里流量方向判断用了固定方向而非压差方向。或者调压器节点被误设为定压节点,导致下游压力被强行抬高。
解决:流量方向必须用np.sign(dp2)动态判断,不能预设。调压器节点如果指定了下游压力,它应该是定压节点,但上游节点压力仍未知,检查节点分类是否正确。
4.4 现象:小管网算得飞快,大管网内存爆掉
原因:雅可比矩阵用稠密矩阵存储,节点数上千时内存 O(n²) 增长。或者每次迭代都重新构建稀疏矩阵,没有复用结构。
解决:用scipy.sparse的lil_matrix或coo_matrix构建,转csr求解。如果拓扑不变,雅可比矩阵的稀疏结构也不变,可以预先分析符号分解,每次迭代只更新数值。
4.5 现象:换了台电脑跑,结果对不上
原因:浮点精度差异、numpy 版本不同导致spsolve的底层库行为不一致,或者随机初值没固定种子。
解决:初值生成用确定性方法,不用随机数。在代码开头固定np.random.seed(42)以防万一。关键结果输出到 CSV 时保留 6 位小数,方便比对。
5. 进阶技巧:用灵敏度矩阵快速回答「如果……会怎样」
静态模拟跑通之后,真正高频的需求是「某分输点加 10 万方/日,末端压力掉多少」。重新跑一遍完整迭代当然可以,但如果你要扫几十个方案,用灵敏度矩阵会快得多。
灵敏度矩阵的本质是:在收敛点附近,节点压力对节点用气量的偏导数。由节点方程 F(p, d) = 0,两边对 d 求导:
J · (dp/dd) = -∂F/∂d其中 J 就是最后一步迭代的雅可比矩阵,∂F/∂d 是一个对角矩阵(每个节点的用气量只影响该节点方程)。解一次线性方程组就能得到所有节点压力对所有用气量的灵敏度。
def sensitivity_matrix(J, free_idx, n): """ 计算节点压力对用气量的灵敏度 返回矩阵 S,S[i,j] = dp_i / dd_j """ # ∂F/∂d 是单位矩阵的负值(因为 F 里减去 demand) dF_dd = -np.eye(n) # 只取自由节点对应的行 dF_dd_free = dF_dd[free_idx][:, :] # 解 J_free · S = -dF_dd_free from scipy.sparse.linalg import spsolve S_free = spsolve(J[free_idx][:, free_idx], -dF_dd_free) # 组装完整灵敏度矩阵 S = np.zeros((n, n)) S[free_idx, :] = S_free return S拿到灵敏度矩阵后,任何用气量变化 Δd 引起的压力变化近似为 S · Δd。我实测过,在收敛点附近 10% 以内的扰动,灵敏度法给出的结果和重新迭代的误差小于 0.5%,但速度快 50 倍以上。这个技巧在做方案比选、找管网瓶颈时特别管用。
注意:灵敏度矩阵只在收敛点附近线性有效,扰动太大(比如某管段流量反向)就不能用了,老老实实重新迭代。
最后说个血泪经验:静态模拟的代码写完后,一定拿一个手算能验证的简单例子(比如三段串联管、一个气源一个用气点)跑一遍,确认压力分布和流量与手算一致,再去碰真实管网。我见过太多人直接上几十个节点的环网,结果错了都不知道从哪查。希望帮到你。
本文还有配套的精品资源,点击获取