☰
MIKE 21随水深变化的底床糙率文件制作:从原理到脚本实践
2026/10/3 10:43:43 网站建设 项目流程

MIKE 21里想给底床糙率加上"水深函数"属性,很多人在这一步卡住,到处搜也搜不到能直接照做的教程。我当年买过的MIKE 21快速入门书里,对糙率这部分也基本是一带而过,只告诉你"在面板里输入一个M值",完全没提怎么处理随水深变化这种更贴近实际的需求。这其实不是MIKE 21不能做,而是需要先理解它的输入逻辑,再根据逻辑去生成一份专门的糙率空间分布文件。这篇文章我就从头到尾把这个过程拆开讲,从原理到两种制作方法,再到最后怎么挂进模型、怎么验证,全部串起来。

1. 先搞懂MIKE 21底床糙率的读取逻辑

1.1 Manning数M和糙率系数n别搞反

MIKE 21里涉及底床糙率时,最常用的是曼宁公式。很多做水文水动力的人对v = (1/n) R^(2/3) S^(1/2)不陌生,公式里的n就是曼宁糙率系数,n越大代表床面越粗糙、水流阻力越大。但在MIKE 21的参数面板里,默认输入值往往不是n,而是曼宁数M,M和n互为倒数:

M = 1 / n

比如野外手册查到的天然河道糙率n=0.035,在MIKE 21里如果选的是"Manning number"类型,要填的就是:

M = 1 / 0.035 ≈ 29

这个换算关系看似简单,实际建模时却特别容易翻车。我见过不止一个初学者把n值直接原封不动填进Manning number的输入框里,导致模型里的糙率比原意大了将近30倍,水位怎么率定都对不上。所以在动手做糙率文件之前,先问自己一句:模型里选的是"Manning number"还是"Manning n"?后面所有文件的取值都围绕同一个标准来。

1.2 "随水深变化"为什么不是内置选项

既然MIKE 21能处理空间变化糙率、时间变化糙率,那为什么不能直接在界面上写一句"糙率等于某个关于水深的函数"?原因在于MIKE 21的核心计算逻辑是按网格单元逐项读参数的。水深在空间上是分布的,同一个时刻,主槽水深5米、滩地水深0.3米,想让糙率跟着水深一起变,本质上是让每个网格单元拿到一个符合它的水深条件的糙率值。软件不是不能算,而是这个"水深与糙率的映射关系"需要你自己提前算好,再写成空间分布文件喂给它。

所以正确思路是:先想办法得到模型范围里的水深分布,再用一整套规则把水深换算成糙率,最后生成一份dfs2或dfsu文件,让MIKE 21在每一时刻从文件里读取对应网格的糙率。记住这条链路,后面的操作就顺理成章了。

2. 制作糙率文件前必须准备好的三个数据

2.1 网格和地形文件

不管你是用结构网格的MIKE 21,还是非结构网格的MIKE 21 FM,糙率文件必须与模型网格在空间上一一对应,也就是说文件里的每个网格单元位置、坐标系、投影都要和模型完全一致。最好的做法是不要新建一个"大概差不多"的网格,而是直接基于模型的地形网格来创建糙率文件。

实际操作中,你先确认手头有以下基础文件:

  • 结构网格:网格定义文件(.mesh)或已经在MIKE Zero里导出的地形dfs2
  • 非结构网格:网格文件(.mesh,注意是FM模型的mesh格式)
  • 水深结果文件或用于推水深的初始水位场

检查网格和地形的时候,重点看单位是米还是坐标单位,很多从CAD或GIS导进来的数据会带着奇怪的坐标系,导致后续文件叠加时出现偏移。MIKE 21对这类问题不怎么给明确报错,往往只是模拟结果不对,排查起来非常痛苦。

2.2 水深基准场怎么来

要知道"随水深变化",就得先有一个水深场。这个水深场可以有两种获取方式。

方式A:跑一个稳态模拟。设置上游入流或者恒定水位边界,下游给定水位边界,让模型稳定运行足够长时间,直到整个计算区域内的水位和流量不再随时间变化。从结果文件里直接提取Water Depth这一项,就得到当前工况下的水深分布。这个方法适合有明确上下游边界条件、关心特定流量级下水深工况的项目。

方式B:如果没有水动力结果,也可以用初始水位减去地形高程来近似。比如整个模型范围你给定一个初始水位5米,地形高程是3米的地方,初始水深就是2米。这种方式简单粗暴,适合用来做"主槽和滩地糙率分区"这种概化程度较高的处理。需要注意,地形高程高于水位的地方水深为负,要单独处理成0或者一个无效值,不能让它参与糙率映射计算。

我自己通常更喜欢用稳态模拟的方式,因为这样得到的水深场与后续要跑的计算工况更接近,特别是研究洪水演进时,不同流量下主槽与滩地的过水面积完全不一样,用同一个静水水深场去定糙率分区会有偏差。

2.3 水深-糙率映射表怎么定

有了水深场,接下来就是确定"多深的水对应多大的糙率"。这一步没有任何软件能替你拍板,必须结合你自己的项目资料。一个典型的平原河道漫滩系统,映射关系可以设置成下面这样:

水深范围(米)曼宁糙率 n对应曼宁数 M
0 ~ 0.50.04025
0.5 ~ 1.50.03529
1.5 ~ 3.00.03033
> 3.00.02540

这个表的逻辑其实很符合水力学直觉:浅水区往往在滩地上,有杂草、灌木甚至建筑物,阻力大;深水区一般是主槽,河床被水流冲刷得比较光滑,阻力小。如果研究区域是山区性河流,河床卵石粒径大,糙率整体要提高;如果是感潮河段,底质较细,糙率整体可以降低。

这里多说一句:映射表只是一个初始方案,最后还要靠实测水位流量过程率定。但好消息是,用文件方式管理糙率后,改糙率只需要重新生成一次文件,不需要动MIKE 21模型主体结构,效率比直接改参数高很多。

3. 没有编程基础也能做的MIKE Zero手动生成法

3.1 用Grid Series Editor做栅格重分类

如果只做一次糙率文件,网格数量也不大,完全可以在MIKE Zero里手动搞定,不用写一行代码。

具体步骤是这样的:

  1. 打开MIKE Zero,选择"Grid Series Editor"模块。
  2. 新建一个dfs2文件,这时需要设置网格属性。强烈建议直接读取现有水深结果文件或地形文件的网格属性,确保坐标系、网格大小、起始点、旋转角完全复制过来,这一步能避免后续"文件读不进模型"的各种低级错误。
  3. 在水深文件中确认数据,通常水深结果文件里可能还有水位、流速等多个item,你自己要知道哪一个item代表Water Depth。
  4. 用Grid Series Editor里的数学表达式功能做栅格重分类。比如可以应用条件表达式:当水深小于0.5米时赋0.04,当水深介于0.5到1.5米时赋0.035,以此类推。
  5. 将计算结果导出或另存为一个新的dfs2文件,这就是初步的糙率空间分布文件。

手动方式的优点在于直观,每一步都看得见;缺点也很明显,一旦做的是非结构网格dfsu,Grid Series Editor不是直接支持的。如果你用的是MIKE 21 FM(非结构网格),建议直接跳到下一部分的脚本方法,或者先用DFS2做,再通过网格插值转成dfsu。

3.2 借助GIS和网格插值工具实现重分类

还有一种"曲线救国"的方式:把MIKE 21的水深结果导出成网格点数据(XYZ或ASCII格式),放到QGIS或ArcGIS里用栅格计算器做重分类,然后再把重分类后的栅格导回MIKE Zero支持的dfs2格式。这个流程对熟悉GIS的人很友好,因为栅格计算器里写条件表达式比MIKE Zero里的数学表达式更顺手,而且可以复用很多现成的地表分类数据。

这里有一个关键提示:从MIKE导出XYZ再导回dfs2,中间会经过一次散点插值,插值方法选不好会改变糙率空间分布的细节,甚至出现某些单元的空值。我一般建议用最近邻或自然邻域插值,不要用克里金或样条,因为糙率本身是分区常量式分布,平滑插值反而会让不同水深区交界处的糙率出现过渡带,那不是你想要的效果。

3.3 手动方法的适用边界

什么时候用手动方法就够了?我的判断标准是:模型网格数量在几万以下,水深-糙率的分段规则不变,只需要生成一版文件供对比计算。这时候用Grid Series Editor或者GIS重分类都行,省时省力。

但当出现下面任何一种情况,手动方式就开始让人抓狂了:

  • 网格超过几十万个,重分类计算时间很长
  • 需要多次调整深度-糙率分段阈值,每改一次就要重做一遍
  • 有多个工况、多个水深基准场需要批量生成糙率文件
  • 想对糙率文件做进一步的统计、平滑、分区处理

这时候就该上脚本了。

4. 批量生成糙率文件:Python脚本才是效率解药

4.1 为什么推荐脚本

DHI这些年开放了不少接口,其中mikeio这个Python库已经相当成熟,可以直接读写dfs2、dfsu、dfs0这些MIKE格式文件。用它来做水深重分类,本质上就是把你在GIS里手动干的活变成可重复执行的代码,好处是:参数调整只改一行阈值,文件批量生成只是循环的事,而且代码跑完的结果确定性高,不容易出现手动操作时的误点漏点。

前提是你的电脑上装好了Python环境,以及mikeio库。安装方式和其他Python库没有区别:

pip install mikeio

如果你还没装Anaconda或Miniconda,建议顺手装一个,后面处理其他水文数据也用得上。

4.2 用mikeio读取水深并生成dfs2

下面这段代码我实际跑过,可以直接参考。假设你已经有一个稳态模拟得到的水深结果文件water_depth.dfs2,我想把它转成曼宁系数n的糙率文件。

import numpy as np import mikeio # 读取水深结果,第一个item假设是Water Depth ds = mikeio.read("water_depth.dfs2") depth = ds.data[0] # 定义水深 -> 曼宁糙率n的映射函数 def n_from_depth(depth): n = np.full_like(depth, 0.030) # 默认值 n[depth < 0.0] = 0.040 # 干单元区域 n[(depth >= 0.0) & (depth < 0.5)] = 0.040 n[(depth >= 0.5) & (depth < 1.5)] = 0.035 n[(depth >= 1.5) & (depth < 3.0)] = 0.030 n[depth >= 3.0] = 0.025 return n n_rough = n_from_depth(depth) # 写回新的dfs2文件 out_items = [mikeio.ItemInfo("Manning n", mikeio.EUMType.Manning_Resistance_Number, mikeio.EUMUnit.manning_resistance_number)] ds_out = mikeio.Dataset([n_rough], time=ds.time, geometry=ds.geometry, items=out_items) ds_out.to_dfs("roughness_n.dfs2")

这里有一个单位问题的提醒:如果你在MIKE 21模型面板里选的是Manning number M,那你应该把n_from_depth的输出取倒数再写进文件,也就是M = 1 / n。如果你选的是"Manning n",就可以直接写n。两种方式都能让模型跑起来,但一定要注意和模型设置匹配,这是我实际踩过最深的坑之一。

4.3 非结构网格dfsu的生成

对于MIKE 21 FM模型的非结构网格,处理逻辑完全一样,只是读写对象从dfs2变成了dfsu。mikeio对dfsu的支持也很成熟,读取后数据形状是"时间步 × 单元数",重分类操作完全兼容。

ds = mikeio.read("water_depth.dfsu") depth = ds.data[0] n_rough = n_from_depth(depth) out_items = [mikeio.ItemInfo("Manning n", mikeio.EUMType.Manning_Resistance_Number, mikeio.EUMUnit.manning_resistance_number)] ds_out = mikeio.Dataset([n_rough], time=ds.time, geometry=ds.geometry, items=out_items) ds_out.to_dfs("roughness_n.dfsu")

需要注意的是,dfsu文件里各个单元的排列顺序是固定的,重分类时不要做任何会改变单元顺序的操作,比如排序、去重。只要你不重排数组,参考模板文件读取和写出就会保持元素一致性。

5. 把糙率文件挂进MIKE 21模型里的正确姿势

5.1 结构网格(MIKE 21)的加载路径

如果你用的是MIKE 21结构网格模块,打开HD参数设置界面,找到"Bottom Roughness"或"Bed Resistance"这一项。这里选择Spatial Variation(空间变化),然后指定刚生成的dfs2文件。界面会要求你选择对应的item序号,如果你生成的文件里只有糙率这一个item,一般就是第1个。

加载后还要注意时间轴的设置。如果糙率文件里只有一个时间步,模型在计算时会把该值视为恒定不变;如果文件里含有多个时间步,MIKE 21会做时间插值。对于"随水深变化"的需求,通常使用单时间步的恒定空间场就够了,因为水深-糙率的转换关系本身就是一套固定的映射,不是随时间任意变化的。

5.2 非结构网格(MIKE 21 FM)的加载路径

MIKE 21 FM的路径略有不同,但也很直观:进入Domain设置,找到Bed Resistance,把类型改为"File",选择dfsu糙率文件。这里同样需要指定item序号。FM模型对这个文件的网格一致性要求更高,因为dfsu的网格和单元顺序必须与模型计算网格完全一致,否则加载时直接报错。

我建议在生成dfsu糙率文件时,直接用模型本身输出的dfsu结果文件作为模板读取,这样网格和单元顺序无论如何都不会错。不要绕道dfs2再插值一次,多一步就多一个出错的可能性。

5.3 坐标、单位和时间轴的三个大坑

这部分全是我实践里见过的问题,专门列出来。

第一个坑是坐标偏移。用Grid Series Editor手建dfs2文件时,很多人不复制原网格属性,结果文件虽然能打开,但导入模型后糙率分布整体偏移了几公里。这种问题在结果上看不太出来,因为模型照样能算,但你会觉得"怎么填了糙率文件水位反而更不对了"。排查方法是用MIKE Animator同时打开地形文件和糙率文件,检查两者的等值线是否重合。

第二个坑是单位。MIKE本身很灵活,但灵活也意味着容易混乱。比如在一个使用Manning number的模型里,你却喂进去一份Manning n的糙率文件,糙率差异会直接导致模型结果失真。我的办法是在文件名里就写清楚是"n"还是"M":roughness_n.dfsu还是roughness_M.dfsu,形成习惯。

第三个坑是时间轴。如果你生成文件时不小心把时间基准、时间步长设得和模型不一致,MIKE 21可能会用插值或取最近值的方式读取,糙率的时间变化就不符合预期。稳妥做法是:用模型可接受的绝对时间起点,文件时间步设置为单步。

6. 文件生成后怎么验证有没有生效

6.1 可视化检查空间分布

糙率文件生成并加载进模型后,不要急着直接跑正式模拟,先做一次简单的可视化验证。用MIKE Animator打开模型的地形文件和糙率文件,把两者并排显示。重点观察糙率分区是否与地形走向一致,浅滩区域是不是确实取了较大的糙率,主槽是不是取了较小的糙率。

如果某处出现零值、负值或者异常大的值,多半是水深场中存在干单元或异常缩放导致的。这些位置在干湿交替计算中虽然不一定被激活,但一旦计算水位上升、干单元变成湿单元,明显的糙率异常值就可能引发数值不稳定,轻则结果跳动,重则直接发散。

6.2 用对照模拟判断糙率文件的合理性

最有效的验证方式是跑一组对照:同一个模型、同一个边界条件,一组用全模型恒定糙率,另一组用随水深变化的糙率文件,然后对比关键断面或站点模拟出的水位、流速过程线。

如果随水深变化的糙率文件是合理的,你通常能看到:低水位时期,浅水区糙率较大,模拟水位比常数糙率方案更高一些,流速更低;高水位时期,主槽深水区糙率起主导作用,两种方案差距缩小。这种规律性差异说明糙率文件确实在计算中发挥作用了,也和你设置的映射关系一致。

如果对照结果完全没有差异,首先要怀疑糙率文件根本没有加载成功,比如item选错了、时间轴不匹配,或者模型里仍然使用默认糙率值。这是我认为最需要建立的一个排查顺序:先确认文件加载,再确认映射逻辑,最后确认取值量级。

6.3 率定时的调整顺序

最后聊一下率定的调参顺序。糙率映射表一般有三到四个参数:浅水糙率、过渡水深阈值、深水糙率。每次调整我建议只动一个参数,比如只调整浅水糙率从0.040改到0.050,其他保持不变。这样你才能从模型响应中判断出"水位偏高是因为滩地糙率偏大"还是"因为深水主槽糙率偏大"。

一个比较实用的经验是:水位偏低优先增大浅水区糙率,水位偏高优先减小深水区糙率。因为水位偏高通常意味着河道的泄流能力不够,问题往往出在主槽阻力被高估了;水位偏低则意味着阻力被低估,浅水漫滩区域往往是主要嫌疑。当然这只是一般规律,具体项目还要结合植被、底质和实测资料来判断。

另外,糙率文件做出来不是一劳永逸的。如果模拟里既有枯水季又有丰水季,水深分布差异很大,建议对不同流量工况各生成一份糙率文件,然后在模型里按时间区间切换。MIKE 21支持时间上分段的文件,利用这个特性,可以让不同时期用不同糙率场,比全局一套糙率更合理。

我在实际项目中体会最深的一点是:糙率文件本质上是你对"水下地形阻力的空间概化"的一套看法,它不是越复杂越好。分三到五个水深区间基本够用,分十几个区间除了增加率定难度,并不会带来明显的精度提升。先跑通、再对比、再逐参数调整,这条路径走下去,随水深变化的糙率文件就能真正为你所用,而不是变成模型里一个"看起来专业但没啥反应"的摆设。

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

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

立即咨询