1. 二维水动力耦合模拟整体思路
1.1 为什么标准SWMM不够用:从汇水区概念到真实地表漫流
干了这么多年SWMM二次开发和内涝模拟,我最常被问到的一个问题就是“管网没问题,为什么积水深还是对不上”。答案通常就藏在一个词里:SWMM的经典架构是一维管网 + 汇水区概念。它骨子里是“半分布式水文模型”,把地表产流汇流抽象成一个个子汇水区的宽度、坡度、糙率,通过下渗曲线和汇流路径把雨水“折算”后送入管网节点。这种做法在排水系统设计工况下够用,因为它回答的是“管道能不能过、节点会不会满溢”的问题,但一旦你要模拟真实的城市内涝,情况就变了。
城市内涝不是只发生在检查井里。水从雨水口冒出来之后,在地面上会沿着道路低点、地形洼地、建筑边界的缝隙漫流,遇到河道顶托还会倒灌,这些行为是真正的二维地表水力过程。如果还用一维汇水区去近似,你只能得出“这个节点溢流量是0.4立方米每秒”这种抽象结论,至于这些水淹没到了哪栋楼、路面积水多深、退水要多长时间,SWMM默认是给不出来的。
所以说,二维耦合模拟的初衷,就是把地表漫流这部分从“汇水区概念”升级成“真实浅水方程求解”。管网仍然用SWMM的一维圣维南方程组去算,地表改用二维网格去解连续性方程和动量方程,在雨水口、检查井这类泄水点做双层模型之间的水量交换。这样模拟出来的流场是连续的:雨水落到二维网格上,经地表汇流进入雨水口,再进入SWMM管网转输,管道满水后溢流回到二维地表继续漫流,整个过程形成一个完整的水循环闭环。
1.2 耦合模式的分类与选型:你该做哪种耦合
我在实际项目里见过不少人一上来就问“你们能不能把二维模型直接嵌到SWMM里面”,其实这里面的“耦合”有几种完全不同的实现思路,先把这个想明白了,方向才不会跑偏。
从实现架构上说,目前工程上常见的大体可以分三类。
第一类:松散耦合(离线耦合)。这种方案是分别建一个SWMM管网模型和一个独立二维地表水模型,两个模型共享地形和边界条件,但是运行是分开的。SWMM先算出溢流流量时间序列,再把它作为二维模型的入流边界导入,二维模型再单独跑一遍。优点是开发成本低、调试简单,两个模型都可以用成熟商业软件来跑;缺点是溢流回灌、管网倒灌这类“双向耦合”行为没法真实模拟,而且时间同步很难做到一致。适合做方案比选、风险普查这类精度要求不高的场景。
第二类:动态耦合(在线耦合)。管网和二维网格在同一个时间框架内同步推进,每个时间步内通过交换节点(通常是检查井、雨水口)实时交换流量,二维网格水位变化反哺给管网边界条件,管网水头变化也实时影响地表溢流。这是工程中做得最多的方案,也是我这篇文章要重点展开的方案。
第三类:完全集成式(单模型)。将一维管道方程和二维浅水方程统一到一个求解器里,不再区分“哪一段是SWMM、哪一段是地表模型”。这在算法上最优,计算效率和鲁棒性都最好,但实现工作量极大,基本上是商业软件团队或大型科研团队才能长期维护的事情。
选哪一类,取决于你的项目要回答什么问题。如果是做流域洪水分析和道路内涝预测,我强烈建议直接选动态耦合,它能反映管网与地表的相互作用,也是在现有SWMM框架上做得动、见效快的折中方案。如果要处理大范围、长时段的城市雨洪仿真,且不追求过程双向影响,那就先做松散耦合,把精力花在模型校准而不是耦合器开发上。
1.3 耦合的业务闭环:两个域的边界在哪里
动耦合落地之前,先把边界问题说清楚。模型域划分为地表域和管网域。地表域是一套二维非结构化网格,通常用三角形或四边形单元覆盖研究区域,地形高程从数字高程模型(DEM)插值到网格中心点。管网域是SWMM的一维节点–管道网络,节点即检查井,管道即管网连管。两个域的交互就发生在那些“看得见”的检查井和雨水口位置。
这里有个容易搞混的点:管网中的节点本身没有几何面积,但在二维网格里它们对应的可能是几个网格单元的集合。实际做法是给每个交换节点分配一个或多个地表网格单元,然后在这二者之间建立一对多的水量交换关系。管网节点的水位代表管道中的水头,地表网格的水深代表地表流场的水位,两侧水位差会驱动水体流动,流向由水位高低决定。
提示:耦合边界不要选在管道的中间位置,应该选在具备实际物理意义的水流入口/出口处,比如检查井、雨水口、河道拍门。否则会出现一维和二维明明在同一坐标却互不感知的尴尬局面。
2. 数值内核与关键机制拆解
2.1 二维浅水方程和SWMM一维求解的根本差异
动态耦合的核心是用二维浅水方程描述地表漫流。标准形式是守恒型浅水方程组,包含连续方程和两个方向的动量方程,底坡项、摩阻项、紊动粘性项都在其中。
连续方程: ∂h/∂t + ∂(hu)/∂x + ∂(hv)/∂y = q_in
动量方程(以x方向为例): ∂(hu)/∂t + ∂(hu² + gh²/2)/∂x + ∂(huv)/∂y = -gh(∂z_b/∂x) - (g n² |u| u) / h^(1/3) + 湍流扩散项
这里面有几个变量需要特别关注。h是水深,u、v是沿x、y方向的流速分量,z_b是地面高程,g是重力加速度,n是曼宁糙率系数,q_in是源汇项,也就是降雨、蒸发和耦合交换水量的总和。
SWMM的一维管网用的是圣维南方程组,求解管段中非恒定渐变流。它侧重的是管道断面的平均流速和水位,不需要区分横断面内的流态细节。这两个方程的数学性质非常相似,都是双曲型守恒律,但到了求解器层面,差别巨大。管网是沿着管段方向的一维计算,网格和拓扑是“线段–节点”结构;地表是二维任意多边形网格,每个单元要与相邻单元计算通量,这涉及网格邻居搜索、干湿界面处理、数值通量格式选择,计算量成倍增长。
另一个关键差别是时间尺度。管网管段中的水流速度通常可以到每秒1到3米,但排水系统短、蓄量小,水在管道里走一圈可能只要几分钟;地表漫流水深浅、糙率大,流速往往不到0.5米每秒,但漫流路径长,积水消退可能持续数小时。动态耦合时,如果两个域用同一个数值时间步长,往往会出现管网算得不够、地表算得浪费的问题。后文我会专门说时间步长的处理。
2.2 网格与技术选型:三角形、四边形还是结构网格
二维网格的选择直接决定计算效率和稳定。我做过几轮对比,简单小结一下。
城市地表漫流模拟大多数情况下不追求精细的贴体网格,用正交结构网格是最省事的。结构网格邻居关系简单,指针寻址快,并行分区也容易。但城市里有复杂的建筑边界、路网、河道走向,用正交网格会让边界变成锯齿状,建筑物没被真实反映,漫流路径也容易失真。
非结构三角形网格能更好地贴合地形和建筑物轮廓,但代价是网格邻居搜索更复杂,数值通量计算要从基于面的方向向量重新计算。四边形非结构网格介于两者之间,形状相近时精度尚可,但生成高质量四边形网格对软件要求偏高。
我的实践建议是:平原城区优先用三角形非结构网格,把建筑占地合并成凸多边形并“挖掉”形成洞,把道路低洼区加密。山洪沟道这种狭长地形,再用规则四边形网格配合加密段。
糙率参数方面,二维地表曼宁糙率不能直接套用管网手册的数值。城市地面糙率一般在0.013到0.05之间,铺装道路、屋面和绿地差异很大。耦合模型中同一个网格内的糙率可以按土地类型面积加权,也可以直接用航拍分类图赋值。实测经验是:糙率调0.005到0.01对水位影响往往不大,它主要影响流速和淹没范围边界的形状。
2.3 侧向交换、雨水口连接与容量限制
管网和地表耦合的点,不是简单配个水位关系就完事。实际工程中我看到三类常见的耦合连接方式。
雨水口(Gully)连接:地表水收水后通过雨水箅子进入连管。收水能力取决于箅子尺寸、堵塞程度和水位差。一般用一个流量–水位差经验公式来近似,比如weir/orifice类型公式。这个收水能力曲线非常关键,径流能不能被管网及时收走,直接决定了地面积水深度的大小。
检查井(Manhole)连接:井盖位置既是溢流发生点,也是回灌点。当管网水位高于地表水位时,水从井中涌出;当地表水位更高时,水回流进井里。这种连接需要定义井盖的几何尺寸,把检查井近似成一个具有进水口和出水口的特殊网格单元。井盖的泄流能力通常用孔口出流公式估算,流量系数在0.6~0.65之间。
蓄水节点/泵站连接:与泵、闸、堰联合工作,常用于一体化泵站和调蓄池的耦合模拟。这里要注意,泵站的水力特性曲线往往是在一维条件下率定的,搬到二维网格边界上要重新检查流量–扬程关系是否还能成立。
注意:耦合位置的“容量”不能只看几何尺寸。雨水口收水能力在暴雨过程中下降很快,树叶、垃圾堵塞会把实际收水能力削掉一半,这在参数率定里一定要留一个最小堵塞因子。
2.4 稳定性、时间步长与CFL条件
二维浅水方程和管网圣维南方程都是显式格式主导的求解器,稳定性的最大约束就是库朗条件(CFL)。对于显格式,一个时间步内信息传播距离不能超过一个网格尺寸:
Δt ≤ α · Δx / (|u| + √(g·h))
其中α是安全系数,一般取0.5到0.8。Δx是网格特征尺寸,u是流速,h是水深。城市地表漫流浅水区的√(gh)项通常很小,但遇到管道急剧过流、网格偏小的地方,这个时间步会很小。举个例子,如果网格平均尺寸5米,流速0.3米每秒,水深2米,√(gh)约4.4米每秒,那Δt≈0.8×5/4.7≈0.85秒。如果网格加密到1米,Δt就降到0.2秒以下。
管网的时间步长受管道中波速影响,城区重力流管道的波速一般在2~10米每秒,管段长度几十米,Δt大概在5秒左右。你会发现地表显式计算的时间步长比管网骨干模型小一个数量级。这就是为什么耦合求解器需要分区步长:管网域用大时间步(例如5秒),地表域用小时间步(例如0.5秒),两者通过插值保持边界交换量在每个小步内平滑变化。
如果你把所有域都压到最小时间步去算,一个中等城市模型可能从半小时变成一晚上,性能完全是两码事。
3. 二次开发落地实操:从数据结构到核心循环
3.1 开发框架与数据组织
做SWMM二次开发,很多人一上来就改SWMM源码,但我建议你先理清自己的需求层次。如果你只是用耦合仿真,不需要改动管网求解核心,那你可以在SWMM的引擎层之上挂一个二维求解器,通过内存数据交换实现耦合。如果你要修改管网的入流/出流边界,那确实需要直接改SWMM源码里的分流逻辑。
这里我推荐一种模块化架构,也是我在多个项目中踩坑后形成的最佳实践:
- 核心层:SWMM引擎的C函数库,负责管网汇流计算;
- 网格层:独立的二维浅水求解器,封装成动态链接库;
- 耦合层:负责两个求解器之间的数据交互、步长同步和边界条件构建;
- 数据层:管理地形、土地利用、管网拓扑、时间序列输入。
网格数据用一维数组存单元中心水深、流速、地面高程,二维邻居表用数组索引而非指针,这样方便向量化。检查井对应的交换单元维护一个单独的索引表,记录每个交换节点关联哪些网格单元。所有时间序列统一用标准格式读入,避免不同模型之间的时间基准错位。
3.2 核心耦合循环的伪代码示意
下面我给一个简化但能说明问题的动态耦合流程伪代码。这个循环基本反映了实际工程中每一步都在做的事:
// 伪代码:SWMM与2D地表模型的动态耦合主循环 for (每个外部时间步 t = 1..T) { // 1. 从SWMM获取管网节点状态 // 每个交换节点的水位 H_node[t] 和可能溢流量 Q_overflow[t] for (每个交换节点 k) { H_node[k] = swmm_get_node_depth(k); Q_overflow[k] = swmm_get_node_overflow(k); } // 2. 地表二维求解器推进多个子时间步 double sub_dt = compute_sub_dt(grid); while (sub_time_elapsed < swmm_step_dt) { // 2a. 边界交换流量 // 根据 H_node - H_surface 的正负,判断溢流或回灌 // 溢流: Q_exchange = orifice_flow(H_node > H_surface) // 回灌: Q_exchange = weir_flow(H_surface > H_node) set_2d_source_terms(exchange_cells, Q_exchange, H_node); // 2b. 二维求解器推进一个子时间步 advance_2d_solver(sub_dt); // 2c. 回写二维水位到耦合边界 // 用于下一个SWMM大时间步的边界插值 sub_time_elapsed += sub_dt; } // 3. 更新SWMM管网边界条件 // 将上一段时间内的平均交换流量,作为节点入流/溢流边界 for (每个交换节点 k) { double avg_Q = compute_average_exchange(k); swmm_set_node_inflow(k, avg_Q); } // 4. 推进SWMM一个外部时间步 swmm_advance(swmm_step_dt); }真实开发中要处理的细节比这个多得多。交换流量的方向判定要考虑水位和井口标高的关系:井口标高不能让负水深淹没节点水头。二维单元也不能在一个子步内被完全抽干,必须加干湿判定阈值,比如水深小于0.001米时冻结该单元流速,防止负水深振荡。
3.3 参数如何定:交换系数、井盖流量系数与糙率的坑
耦合模拟最让人头疼的往往是那几个不是直接出流公式的“经验系数”。我建议从以下几条开始定参数:
井盖流量系数:检查井溢流在二维模型里可以当薄壁堰或孔口处理。我做实测对比时发现,完整溢流状态(井盖完全顶起)时流量系数更接近孔口公式0.6,而半淹没状态介于0.65~0.75之间。这看似差异不大,但在暴雨峰值时,流量系数差了0.1就可能让峰值流量差15%。
雨水口收水能力:这块不确定性最大。理论公式只能给个范围,最好用当地排水规范的经验收水率曲线。如果项目没有实测数据,建议采用动态堵塞因子,比如0.8起步,暴雨峰值降到0.4~0.5。
曼宁糙率:二维地表糙率按土地类型赋值后,不要全局乘一个缩放因子。有的团队喜欢统一乘0.8来“校准”淹没范围,这不物理。更可靠的做法是:绿地糙率取0.05~0.08,道路取0.013~0.017,不透水屋面取0.02~0.025,然后只用一到两个敏感网格组做局部调整。
参数率定顺序也有讲究。先率定管网流量过程线(用管道流量计和液位计数据),再率定二维淹没范围(用积水点位和无人机影像),最后微调交换系数。如果一上来就调二维糙率去匹配峰值流量,大概率会把地表过流能力调得面目全非。
3.4 性能优化:内存、并行与计算网格
城市尺度二维模型动辄几十万到上百万网格单元。显式浅水方程求解器的计算热点是通量计算,占整体耗时八成以上。我常用的优化手段有几个:
第一,向量化。通量计算尽量写成连续数组上的循环,避免在邻居循环里做条件分支。编译器能自动向量化的代码,比手写优化快3倍以上。
第二,网格静态分区+MPI并行。城市管网拓扑相对固定,可以让每个分区持有自己的网格块和对应的井-网格连接表。唯一需要跨进程通信的地方是分区边界的通量交换。这里要注意负载平衡,建筑密集区网格单元小、地形复杂,应该比平坦区分配更少的网格。
第三,自适应时间步。显式求解器的稳定性限制是局部最大波速,但实际某个时间段只有一个局部的管道满溢导致的速度尖峰。可以用分裂方法:普通区域用较粗子步,尖峰区域做局部加密。不过这会让代码复杂度直线上升,项目周期紧的时候先别上,用全局较小步长就行。
内存方面,显式浅水方程每一网格单元要存变量有十来个,一百万个网格大约占一两百兆字节,这在今天的机器上不是瓶颈,但如果两侧求解器同时载入完整地形和管网数据,要注意内存峰值。我建议网格高程和土地类型直接用float存储,计算时再转double,能省一半内存且精度完全够用。
4. 典型问题排查与复盘实录
4.1 耦合发散与质量不守恒:十有八九出在交换边界
耦合模拟最常见的崩溃场景是:跑了几分钟后水位突然变成负的大数,或者全程总水量莫名增了百分之几。排查顺序我反反复复强调过:
先查交换边界的流量方向是否和节点状态自洽。很多新手会在SWMM节点溢流量为正时直接把它加到二维网格上,却没有严格根据井口水位差重新分配。步骤应该是:管网节点持续溢出时,先把溢流总量均匀分配给对应的地表单元;但如果地表水位已经比井口标高高,那部分溢流水应该被抵消掉,否则双重计算导致水量增加。
再看干湿界面。二维单元水很浅时,水深参与动量方程的分母,会放大数值误差。处理办法是设置最小水深阈值,低于阈值的单元冻结动量更新,只算水量平衡。但阈值也不能太大,否则会出现水“爬”不过去的假象,我常用1e-3米作为冻结阈值。
经验:如果模拟结束时水量守恒误差超过1%,不要急着调精度,先检查是不是耦合边界算了两遍流量。我在一个项目里排查过整整一周,最后发现是SWMM的溢流量已经把地面格子的入流算了一次,我又在自己的耦合层里加了一次,水量翻倍。
4.2 计算慢到怀疑人生:步长和网格才是关键
很多团队第一次跑百万网格耦合模型,都会被速度吓到。我见过有人用5米网格跑一个50平方公里城市模型,单步子时间步0.05秒,模拟一场两小时暴雨跑了三天三夜。原因就是网格太大、水深变化太大导致CFL限制极严。
这种问题要从两个方向解决:
一是在合理精度内粗化网格。5米网格其实能给出足够准确的淹没范围和道路积水信息了,不要动不动加密到1米。只有在需要精确模拟建筑漫流边界、或特定积水点时才做局部加密。
二是采用自适应子步长。管网大步长固定为5秒,地表子步长根据当前最大流速动态调整,通常可以在0.2秒到2秒之间浮动。实现时可以做两步预判:第一步计算最大允许子步,第二步推进,返回实际用时。城市暴雨峰值时子步短一点,退水阶段子步自动拉长,总计算时间能减少一半以上。
如果我做性能预算,一般以“实时率”来衡量:模拟时间和计算时间的比值。百万网格配多核并行,做到实时率0.1到0.2(即模拟2小时暴雨,计算约10到20小时)就满足设计校核需求了。如果要跑实时预报,就得考虑GPU加速或者降分辨率。
4.3 结果怎么解释才靠谱:淹没范围、积水深度与风险分级
算完模型不等于做完了模拟。二维耦合输出数据量巨大,一百万个网格每个时间步输出水深和流速,一小时的模拟能产生几十GB结果。这里建议分层输出:
- 特征时刻切片:选择洪峰时刻、最大淹没时刻输出全网格水位/流速场;
- 统计极值场:输出每个网格的最大水深、最大流速、淹没历时;
- 指数合成:按内涝风险公式,比如“水深×流速”积水风险指数,分成蓝、黄、橙、红四级。
关于结果合理性,我有几个判断经验:
第一,不要迷信最大水深图。城市道路上的薄层积水(两三厘米)在图上看着红线一大片,实际风险不高。建议先把淹没范围按水深阈值分层,再结合道路矢量图做道路淹没分析。
第二,二维模型对硬边界(建筑)和软边界(糙率)的敏感性很高。如果建筑淹没结果异常,先检查建筑轮廓有没有切对,再检查建筑边界处网格是否足够细。建筑边界的锯齿会导致流态分离,局部水深异常偏高。
第三,耦合模型中管网回流导致的地面积水,在时间上往往比单纯暴雨产生的积水更“后置”。在做排水防涝预案时,要特别注意看“外江水位顶托 + 强降雨”这种组合工况,因为它会诱发大面积长时间积滞水,单靠二维降雨径流模型根本模拟不出来。
4.4 常见问题速查表
| 问题现象 | 可能原因 | 处理建议 |
|---|---|---|
| 模拟开始后水深剧烈振荡 | 初始水位与干地形不兼容 | 给全网格设置微小初始水深(如0.002m) |
| 管网点持续溢流但地面没水 | 雨水口连接高程错误 | 检查交换节点高程与相邻网格高程是否匹配 |
| 二维网格大面积干涸后无法再湿润 | 干湿阈值设置太大 | 将干湿阈值降至1e-4~1e-3米 |
| 水量守恒误差持续增大 | 耦合边界流量重复计算 | 统一在单一函数内完成流量交换计算 |
| 道路积水范围明显偏大 | 网格过粗,道路被平均化 | 对道路和低洼地带做网格局部加密 |
| 计算时间随模拟时间非线性增长 | 大量单元发生干湿交替 | 开启干湿单元冻结,并增加贝克变换系数 |
| 回灌流量过大导致管网倒灌 | 检查井回灌流量系数偏大 | 将回灌流量系数调低并增设阻抗系数 |
5. 几条实战总结与延伸想法
做二维水动力耦合模拟这几年下来,我最深的体会是:技术难点不在求解器本身,而在两个模型之间那条“缝”。管网模型已经发展了半个世纪,二维浅水求解器也成熟到满天飞,真正决定工程成果成败的,是你敢不敢把边界条件、交换系数、网格衔接这些细节抠到底。
如果让我给刚开始做这个方向的人一个建议,那就是先从一个小算例开始,手动构造一个最简单的“单雨水口 + 十来个管道 + 几百个网格”模型,把整个耦合循环跑通,观察一个溢流峰值的传播过程。这个小算例能让你把前面所有理论点串起来,等到你真刀真枪建城市大模型时,才不会迷失在网格和参数的汪洋大海里。
最后再分享一个小技巧:做耦合模型时永远保留一份“不耦合”的基准模型。跑排查问题时,先关掉耦合边界的交换流量,看看管网单独运算是否正常,再看二维网格单独跑纯地表降雨是否正常。如果两边单独都正常、耦合后才出错,那问题一定出在你写的耦合逻辑里。这个排查法帮我省了无数个小时的调试时间,希望你也能少走弯路。