最近总有做城市气候模拟或建筑能耗方向的朋友,抱着同样一张截图过来问我:WRF-BEM的参数到底在哪改?为什么别人论文里的建筑高度、空调设定温度都填进去了,跑出来的结果却纹丝不动?这个问题我当年也卡了好几天,后来把模块源码和URBANPARAM.TBL表格对照着读了几遍,才算把“WRF-BEM相关参数”这件事彻底理顺。
这篇文章不打算照抄某个版本的官方参数表,那样换个版本就废了。我会把参数到底存在哪儿、哪些参数真正影响结果、改完怎么验证、踩过哪些坑,一次性说清楚。内容适合正在跑WRF多层城市方案、想把BEP-BEM应用到具体城市或建筑能耗研究的读者,也适合刚接触城市冠层模型、被一堆缩写和表格吓到的新手。
1. 先弄懂WRF-BEM的组件关系,参数才不至于改错地方
1.1 BEP和BEM是两个层次的东西
很多新手第一个误解,就是把WRF-BEM当成一个独立的可执行程序。实际上WRF里没有叫“WRF-BEM.exe”的东西,它是城市物理方案BEP(Building Effect Parameterization)加上建筑能耗模型BEM(Building Energy Model)的组合,在namelist里对应的开关是sf_urban_physics=3。
BEP是多层城市冠层参数化方案。它把城市简化成行列式建筑群,在垂直方向上分若干层,逐层计算街道峡谷里的风、温度、湿度和动量交换。这比单层城市冠层模型(UCM)更精细,能模拟出不同高度上的差异,而不是把整个街区当成一个薄片。BEM则是在BEP的框架上,把建筑内部也纳入计算。BEP只关心建筑外表面与外界的热交换,BEM还会算墙体内外传热、室内空气温度变化、人员灯光设备产生的热量,以及空调系统为了维持设定温度而消耗的能量和向室外排放的冷凝热。
在夏季城市热岛研究里,空调冷凝热是一个不可忽略的项。空调把室内的热带到室外,等于在城市边界层里注入了一份额外的显热。这就是为什么很多研究要用sf_urban_physics=3而不是2的原因——BEP算不了这部分,BEM可以。
1.2 参数分散在三个位置,不是改一个文件就完事
WRF-BEM的参数不在一处,我把它分成三个位置来记:
| 参数位置 | 管什么 | 修改后是否需要重新编译 | 修改方式 |
|---|---|---|---|
| namelist.input | 是否启用BEP-BEM、与哪些物理方案搭配 | 否 | 重新运行real.exe并冷启动 |
| URBANPARAM.TBL | 建筑几何、材料热物性、内部负荷、空调参数 | 否 | 改表文件后重启wrf.exe或restart |
| module_sf_bep_bem.F | 垂直分层数、部分固定默认值、算法细节 | 是 | 改源码后重新编译 |
不少人只盯着URBANPARAM.TBL改,改完发现namelist里sf_urban_physics还是默认的0,那整个模块压根没被激活,参数自然不生效。反过来,也有人只在namelist里把开关改成3,却不检查表文件里的城市类型对应关系,结果城市网格点上的参数全是默认值,等于白跑。
1.3 三个位置的读取顺序与生效时机
了解“什么时候被读取”比“参数叫什么”更重要。real.exe生成初始场的时候,会根据namelist里的sf_urban_physics判断要不要在城市网格点上初始化城市变量,包括屋顶温度、道路温度、室内温度这些状态量。wrf.exe正式运行时,才去读取URBANPARAM.TBL里的静态参数。
有个很常见的操作误区:改了URBANPARAM.TBL以后,觉得只是改了表,直接接着原来跑好的wrfout做restart就没问题。实际上,如果你的修改涉及建筑高度、层数、屋顶反照率这些,最好重新走一遍real.exe做冷启动。因为restart会把旧的城市状态量直接读进来,新参数和旧状态混在一起,头几个小时的结果会出现明显的跳变。参数这种东西,最忌讳新旧状态混用。
2. namelist中真正被BEM用到的开关组合
2.1 sf_urban_physics取值含义
sf_urban_physics这个变量,在WRF的&physics部分里。它的取值和含义非常简单:
- 0:不启用城市冠层方案
- 1:单层城市冠层模型(UCM)
- 2:多层城市冠层方案BEP,不带建筑能耗
- 3:多层城市冠层方案BEP外加建筑能耗模型BEM
做建筑能耗和城市热岛反馈的研究,一般直接用3。有的人会疑惑,为什么已经有2了还要有3,区别就在BEM是否计算建筑内部热过程以及空调能耗。如果只关心风场和温度场,不太关心空调负荷,用2也能跑;一旦你想分析建筑能耗随气候变化的响应,或者想把空调废热放进城市能量平衡里,就必须用3。
这个参数是按域设置的,d01、d02、d03可以分别指定不同的值。但实际项目中我不建议相邻嵌套域差异太大。比如d01用0,d02用3,两个域之间城市信息的过渡会非常生硬,侧边界附近容易出现虚假的梯度。
2.2 与陆面、辐射、边界层方案的配合
启用sf_urban_physics=3之后,物理方案搭配要特别注意。下面这几条是我在实际跑通后总结出来的。
第一,陆面过程方案建议使用Noah或Noah-MP。BEP-BEM的城市格点上虽然不依赖传统的土壤分层,但城市与自然下垫面的过渡区域仍然需要陆面过程来处理。选太冷门的方案,可能连城市变量兼容性测试都过不了。
第二,土地分类数据必须包含城市类型。BEP-BEM的城市类型分三类:低密度住宅、高密度住宅、工业/商业区。这些类型要能从下垫面数据里识别出来。通常用MODIS或者USGS分类数据,城市和建成区的类别号要提前确认。很多人在geogrid阶段没注意城市分类是否存在,结果整片区域全是自然植被,表参数再精确也没用。
第三,边界层方案选YSU、MYJ这类主流方案基本没问题。以前有人用某些非局地闭合方案跑多层城市冠层,会出现夜间城市温度异常偏高的情况。这部分大多不是BEM参数的问题,而是PBL对多层城市形态的响应差异。如果你只是想验证BEM参数,建议先用YSU跑通,再去折腾其他方案。
2.3 初始化的一致性检查
改sf_urban_physics之后,最容易翻车的环节是初始化。我遇到过的情况是:修改前用的是sf_urban_physics=0,修改后改成3,然后偷懒不重新跑real.exe,直接用旧wrfout做restart。结果wrfout里压根没有城市变量,温度场出现大片缺失值。
正确的顺序是:修改namelist后,删掉旧的wrfinput_d01这类初始文件,重新运行real.exe,再冷启动wrf.exe。如果非要restart,也得保证重启时刻的城市状态量存在,并且已经跑过一段时间让城市方案稳定。这个“稳定”时间取决心态,至少也要十几个模拟小时。
3. URBANPARAM.TBL按类目拆解
3.1 表结构和城市类型对应关系
URBANPARAM.TBL是BEP-BEM最核心的参数文件。文件名虽然叫URBANPARAM,但内容基本都是给BEM用的建筑参数。打开文件后,你会看到数据按城市类型分组,通常就是低密度住宅、高密度住宅、工业/商业三大类。每个类型下面是一串数值,不同的列分别对应建筑高度、每层高度、墙体导热系数、屋顶反照率、室内产热密度、空调设定温度等等。
不同WRF版本的表头注释写得详细程度不一样。我建议拿到一个新版本先不要急着改,用文本编辑器打开,把注释完整看一遍,确认字段顺序。不要用Excel直接另存为,否则可能把换行符或分隔符改乱,导致wrf.exe读取时出现错位。这类问题很难排查,因为报错信息可能只是一句格式读取失败。
对照城市类型时要注意,表的行顺序和地理数据里的城市类别序号不一定一致。必须以表里的城市类型标识为准,而不是只看第几行。
3.2 几何与材料热物性参数
建筑几何参数决定了城市形态。这里我按自己的习惯分成两类:
一类是建筑单体几何,包括建筑高度、每层高度、建筑宽度、街道宽度、窗墙比。窗墙比是最敏感的参数之一,它直接影响太阳辐射进入室内的比例。窗越大,白天得热越多,空调冷负荷越大。做夏热冬冷地区模拟时,窗墙比差0.1,建筑能耗结果可能差出10%以上。
另一类是材料热物性参数,包括墙体、屋顶、楼板各层的导热系数、比热容、密度和厚度。表面反照率和发射率也在这里设置。材料参数决定了建筑围护结构对温度的缓冲能力。厚墙和保温层可以显著降低室内温度波动,这在BEM里会直接体现为空调负荷的日变化幅度变小。
很多默认表里的建筑高度和材料参数来自欧洲或北美的典型建筑,拿来模拟中国城市之前一定要做本地化调整。国内住宅、办公楼、商业综合体的层高和墙体结构差别很大,盲目照搬会让地表感热通量出现系统性偏差。
3.3 内部负荷与人员活动参数
内部负荷是BEM区别于BEP的另一个关键点。BEP认为建筑内部温度是给定的,BEM要自己算室内温度,就必须知道屋里有哪些热源。
内部负荷通常包括人员散热、照明散热和设备散热,单位是W/m²。这些数值不是全天恒定不变的,往往还有逐小时或按工作日、周末区分的作息曲线。商业区白天人多,设备、照明全开,夜间接近于零;住宅区则是早晚两个高峰。
我刚开始跑的时候,图省事把室内负荷设成全天常数,结果空调负荷完全没有日变化,2米气温的日较差也比观测小很多。后来改成典型作息曲线,日变化才出来。所以如果你发现模拟的城市温度比站点观测平缓很多,优先检查这里,而不是去调边界层方案。
3.4 空调系统与室内设定参数
空调系统在BEM里通常用理想化模型,不模拟压缩机等具体设备,只抽象出几个关键参数:制冷/制热设定温度、能效比COP、最大制冷/制热能力、空调一天内的开启时段。
设定温度对能耗的计算结果影响极其显著。设定温度差1℃,冷负荷就能差出一大截。默认表里不同城市类型的设定温度可能不一样,商业区往往比住宅区设定得更低,这在炎热的夏季会导致商业区上空出现更强的热岛效应,同时伴随更高的空调能耗。
COP代表空调能效。COP越高,消耗相同电力产生的冷量越多,向室外排放的废热也越少。在能耗模拟里,这直接决定建筑用电量和废热排放比例。做能耗评估时,COP值要参考当地空调能效标准来设,不能直接用国外默认值。
4. 从默认参数改出一组可用的实验参数
4.1 最低限度需要改的参数
如果你只想知道“参数改了有没有用”,没必要一上来就把整套参数全部本地化。先改最小集合,跑通流程再说。我建议的第一次改动只有四项:建筑高度、屋顶反照率、窗墙比、制冷设定温度。
这四项分别对应城市形态、辐射收支、建筑得热、空调能耗四个核心环节。改完之后对比wrfout里的2米气温、地表感热通量和空调能耗输出,观察响应方向是否正确。比如屋顶反照率调高,白天地表温度应该下降;制冷设定温度调高,空调能耗应该下降。方向对了,说明参数链路是通的,后面再深入调整。
4.2 不同气候区怎么取舍
如果做真实城市实验,参数取舍要看气候区。下面这张表是我自己常用的调整优先级,供参考:
| 气候类型 | 优先调整的参数 | 原因 |
|---|---|---|
| 寒冷地区 | 室内供暖设定温度、墙体保温性能 | 热损失是主要矛盾 |
| 夏热冬冷地区 | 窗墙比、制冷设定温度、COP | 夏季隔热与冬季采暖都要兼顾 |
| 炎热干燥地区 | 屋顶反照率、制冷设定温度 | 太阳辐射是最大负荷源 |
| 湿热地区 | 通风换气参数、空调运行时段 | 潜热负荷占比明显偏高 |
举个例子,一个南方城市的高密度住宅区,屋顶反照率从0.3改到0.6,夏季峰值冷负荷能下降不少,但夜间温度改善不一定明显。因为夜间的主要热源是白天被建筑墙体储存的热量,这时就需要检查墙体的热容积和导热系数,而不是继续盯屋顶反照率。
4.3 敏感性实验的次序和记录
做参数敏感性实验时,最大的坑是一次改太多参数,结果分不清哪个参数导致哪个现象。我的习惯是one-at-a-time,一次只动一个变量,并且每改一组就单独保存一份URBANPARAM.TBL副本,命名里带上参数名和日期。
另一个容易被忽略的问题是嵌套域。如果d02启用了BEP-BEM,d01没有启用,那么d02里的城市参数变化会通过侧边界影响d01,再反馈回来。这种情况下分析敏感性会非常复杂。所以我做参数分析时,通常只跑一个单域,或者让所有域保持一致的城市参数设置,避免边界反馈把结果搅浑。
5. 改完之后不要只肉眼看图,要盯住这些输出变量
5.1 wrfout里BEM相关变量的核对办法
跑完之后,先别急着画地图,我习惯用ncdump或Python打开wrfout,检查以下几类变量是否存在,数值是否在合理范围:
- 城市地表变量:例如TR_URB2D、TH_URB2D,分别代表城市屋顶温度和城市冠层内温度
- 建筑能耗相关:例如AC_URB2D,代表空调能耗或空调废热相关的输出
- 城市辐射变量:例如URBAN_ALB、URBAN_EMISS,代表城市反照率和发射率
如果这些变量全都不存在,说明sf_urban_physics没有正确生效,或者real.exe初始化时没有生成城市场。如果变量存在但全是零或缺失,说明城市格点没有被识别,多半是土地分类数据里没有城市类型。
温度变量我一般看数量和空间分布是否符合常识。比如夏季午后屋顶温度明显高于气温,夜间道路温度缓慢下降,室内温度维持在空调设定温度附近,这些都能判断BEM是不是在正常干活。
5.2 把建筑模块画成矩形来检查布局
除了看数据,自己动手把建筑布局画出来,也是一种很有效的验证方式。BEP-BEM的行列式建筑布局在水平面上就是一排排矩形,实心矩形代表建筑实体,空心矩形代表中庭、天井或开放的内部空间。检查这些矩形的位置和尺寸是否和URBANPARAM.TBL里的几何参数一致,能直观发现建筑高度、街道宽度设置错位的问题。
这里顺便说一个常见的小任务:根据参数画一个空心或实心的矩形。输入一行四个参数,通常有两种解释——要么是左下角坐标和长宽,要么是两个对角点坐标。下面是Python的简单实现:
import matplotlib.pyplot as plt from matplotlib.patches import Rectangle def draw_rect(x1, y1, x2, y2, filled=True): w = abs(x2 - x1) h = abs(y2 - y1) rx = min(x1, x2) ry = min(y1, y2) rect = Rectangle((rx, ry), w, h, fill=filled, edgecolor='black', facecolor='gray') ax = plt.gca() ax.add_patch(rect) ax.set_xlim(rx - 1, rx + w + 1) ax.set_ylim(ry - 1, ry + h + 1) ax.set_aspect('equal') plt.show() # 实心矩形,参数分别代表左下角x、y、右上角x、y draw_rect(0, 0, 20, 12, filled=True) # 空心矩形 draw_rect(25, 0, 40, 12, filled=False)这套代码在验证建筑足迹时很实用。你可以把不同城市类型的建筑宽度、街道间距读出来,批量生成整个街区的矩形示意图,直接和卫星影像叠在一起看。如果矩形形状和真实街区差异太大,说明表中的几何参数需要调整。
5.3 高频运行错误与参数对应关系
参数问题暴露在运行时,往往不是温柔地报一个“参数错误”,而是直接崩溃或者输出奇怪结果。我把最常见的几类现象列在下面:
| 报错现象 | 可能原因 | 处理思路 |
|---|---|---|
| 读取URBANPARAM.TBL失败 | 分隔符或列数被改动 | 用原始文件对比,重新整理格式 |
| 城市格点全部不参与计算 | 土地分类数据没有城市类型 | 检查geogrid静态数据,确认城市类别存在 |
| BEM输出始终为0 | sf_urban_physics未设为3 | 检查namelist,重新运行real.exe |
| 结果对参数修改没反应 | 改错版本的表文件,或进程读取的不是当前目录的表 | 确认运行目录与文件路径,打印表读取信息 |
| 数值出现NaN | 某参数为0导致除零,或内部负荷设置异常 | 逐步二分排查参数项 |
排查这类问题时,我通常会在URBANPARAM.TBL里改一个非常夸张的值,比如把屋顶反照率改成0.99,看输出里城市屋顶温度有没有相应变化。这比直接看报错信息更快。如果夸张值都没反应,那说明参数链路某处断了,需要回到namelist和初始化去查。
6. 我从实际项目中总结的参数使用经验
6.1 宁可少改,也要一步一验
跑BEP-BEM这些年,我最深的体会是:参数不在于改得多,而在于每次修改都能对应到一个可解释的响应。改完一个参数,一定要追问一句:这个变化合理吗?是往应该的方向变吗?如果方向都不对,多半不是参数值的问题,而是方案配置或数据准备的问题。
比如我见过有人把建筑高度调到200米,模拟一个普通住宅区,结果城市冠层内风速直接变成零。这从物理上就不合理。参数不是越大越能体现城市效应,而是要和实际研究区一致。
6.2 保存好TBL文件的版本记录
URBANPARAM.TBL只是个文本文件,没有任何版本管理。我在项目里吃过亏,跑了几百个小时的模拟,结果发现用的表文件被不记得什么时候改过,所有的结果对不上号。现在我的习惯是:每跑一个批次模拟,就把对应的URBANPARAM.TBL复制一份到独立的结果目录里,并且和namelist.input放在一起。
如果可能,把每次改动的参数写进一篇简短的项目笔记,包括修改日期、修改原因、预期影响。过几个月回来看,你会感谢当时的记录。
6.3 BEM参数调整要结合观测数据,不只是调出来好看
最后说一句容易被忽略的话:参数本地化的最终标准是模拟结果和观测对得上,而不是和某篇论文的参数一致。每个城市的建筑风格、生活习惯、空调使用方式都不一样,哪怕同一个参数,在上海和广州也未必取一样的值。
尽可能找到研究区域附近的气象站点数据、卫星地表温度甚至当地统计年鉴里的建筑密度、人均居住面积作为参照,让参数从来源上就是有依据的。这样即使模拟结果有偏差,你也能说清楚偏差出在哪个环节,而不是陷入不停调参的泥潭。
我个人在实际操作中还有个习惯,就是在跑正式实验前,把URBANPARAM.TBL里的所有参数打印出来核对一遍。有些参数在表文件里存在但模块未必启用,有些参数看起来很重要但对当前模拟时段影响不大。花十分钟做一次清单核对,往往能省下后面几天的排查时间。