WRF 跑区域气候或者短期预报的时候,很多人卡在第一步——把外部数据喂进 WPS。GFS 数据有现成的 Vtable,直接link_grib.csh加ungrib.exe就完事,但一旦换成 ECMWF 的 ERA5 或者业务预报场,尤其是要引入海温(SST)这种下边界强迫,现成 Vtable 就不够用了。ECMWF 的 GRIB 里海温字段命名、层次、时间维度跟 NCEP 那套完全不是一回事,ungrib 认不出来,metgrid 里 SST 就是空的,最后跑出来的结果海陆温差离谱,沿海站点误差能飙到好几度。
这篇东西就是把我自己折腾 ECMWF 海温数据接进 WRF 的完整过程摊开讲。从 Vtable 到底在 ungrib 里扮演什么角色,到怎么根据 ECMWF 的 GRIB 表反查字段、手写映射、验证输出,再到几个我踩过不止一次的坑。适合已经能跑通 GFS 流程、想换 ECMWF 数据源但被 Vtable 卡住的人。如果你连 WPS 都没装好,建议先把基础流程走通再来看这篇,不然容易越看越乱。
1. 先搞清楚 Vtable 在 ungrib 里到底干了什么
1.1 ungrib 不是解码器,它是"翻译官"
很多人对 ungrib 有个误解,以为它负责把 GRIB 解码成 NetCDF。其实不是。GRIB 文件的解码是 WPS 内部依赖的 GRIB 库(通常是 grib_api 或者 eccodes)在做,ungrib 真正干的事是把 GRIB 里五花八门的字段名,翻译成 WRF 认识的统一中间名。
这个"统一中间名"就是 Vtable 里定义的那套东西,比如SST、TT、UU、VV、PSFC、SOILHGT这些。WRF 的 metgrid 和 real 只认这套名字,它不管你原始数据是 ECMWF、GFS 还是 JMA 的。所以 Vtable 的本质是一张映射表:左边是 WRF 要的中间名,右边是数据源里实际的 GRIB 字段标识。
ECMWF 的数据在这张表上跟 GFS 差异很大。GFS 的字段号是 NCEP 自己那套(比如海温是TMP在 surface),ECMWF 用的是 WMO 标准的 GRIB 参数表,海温对应的是参数号 34,短名sst,层次类型是 surface。如果你直接拿Vtable.GFS去 ungrib ECMWF 数据,ungrib 会找不到匹配项,日志里一堆Unknown或者干脆跳过,最后FILE:*里 SST 就是缺的。
1.2 为什么海温字段特别容易出问题
海温在 WRF 里属于下边界强迫,它不像温度、风场那样每个层次都有,而是只在表面出现一次。ECMWF 的 ERA5 里,海温字段有几个特点让新手特别容易翻车:
第一,它可能不在你下载的那批变量里。ERA5 单层数据里 SST 的变量名是sea_surface_temperature,很多人下载的时候只勾了 2m 温度、10m 风、海平面气压,忘了勾 SST,结果 ungrib 跑完发现没有,还以为是 Vtable 写错了。
第二,它的时间分辨率可能跟大气场不一致。ERA5 的 SST 在早期版本里是 6 小时一次,后来改成 hourly,但如果你下载的时候选了不同的 product type,时间戳可能对不上,ungrib 按时间合并的时候就会错位。
第三,层次类型容易搞混。ECMWF 里 SST 的 level type 是Surface,但有些数据集里它可能挂在sfc或者别的类型下,Vtable 里level_type那一列写错了,ungrib 就匹配不上。
我见过最典型的情况是:Vtable 里 SST 那行写的是SST | 34 | sst | Surface,但实际数据里 level type 是sfc,结果 ungrib 日志里 SST 一直是not found,metgrid 跑完met_em文件里 SST 全是默认值。
1.3 Vtable 的列结构逐列拆解
标准 Vtable 是竖线分隔的几列,以Vtable.ECMWF为例,一行大概长这样:
SST | 34 | sst | Surface | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0从左到右依次是:
- 第 1 列:WRF 中间名,比如
SST、TT、UU。这个名字是 metgrid 认的,不能乱写,必须用 WPS 文档里定义的那套。 - 第 2 列:GRIB 参数号(parameter number)。ECMWF 的 SST 是 34。
- 第 3 列:GRIB 短名(shortName)。ECMWF 里是
sst。 - 第 4 列:层次类型(level type)。SST 是
Surface。 - 第 5 列:层次值(level value)。表面字段一般是 0。
- 后面若干列:留给特定数据源的扩展字段,ECMWF 一般用不到,填 0 或者留空。
关键点在于:第 2、3、4 列必须跟实际 GRIB 文件里的元数据完全一致。差一个字符,ungrib 就匹配不上。所以写 Vtable 之前,第一件事是先把 GRIB 文件的字段清单 dump 出来看。
2. 动手之前:把 ECMWF 数据的字段清单摸清楚
2.1 用 grib_ls 把字段全列出来
假设你下载的是 ERA5 单层数据,文件名类似era5_sfc_20200101.grib。第一步不是急着写 Vtable,而是先看这个文件里到底有什么。
如果你装的是 eccodes,用:
grib_ls -p shortName,paramId,levelType,level,dataDate,dataTime era5_sfc_20200101.grib如果用的是老版 grib_api,命令换成:
grib_ls -p shortName,paramId,typeOfLevel,level,dataDate,dataTime era5_sfc_20200101.grib输出大概长这样:
shortName paramId levelType level dataDate dataTime sst 34 surface 0 20200101 0 t2m 167 surface 0 20200101 0 u10 165 surface 0 20200101 0 v10 166 surface 0 20200101 0 msl 151 surface 0 20200101 0 ...这里你要重点确认三件事:SST 的shortName是不是sst,paramId是不是 34,levelType是不是surface。如果levelType显示的是sfc而不是surface,那 Vtable 第 4 列就得写sfc,不能写Surface。
提示:不同版本的 eccodes 对 levelType 的命名可能不一样,有的显示
surface,有的显示sfc。以你实际 dump 出来的为准,不要照抄网上的 Vtable。
2.2 确认时间维度和步长
海温数据的时间戳必须跟大气场对齐,否则 ungrib 按时间合并的时候会出问题。用:
grib_ls -p shortName,dataDate,dataTime,stepRange era5_sfc_20200101.grib重点看 SST 和 t2m、u10 这些字段的dataDate、dataTime是不是一致。如果 SST 是 6 小时一次而大气场是 1 小时一次,那你在下载阶段就得把它们统一到相同时间分辨率,或者在 ungrib 之前用工具做时间插值。
我自己的习惯是:下载阶段就把所有变量统一成相同的时间步长,宁可多下一点数据,也不要在 ungrib 阶段做时间对齐,那个坑更深。
2.3 检查 GRIB 版本和编码方式
ECMWF 的数据有 GRIB1 和 GRIB2 两种。ERA5 一般是 GRIB1(虽然官方也提供 NetCDF),但业务预报场可能是 GRIB2。Vtable 对 GRIB1 和 GRIB2 的匹配逻辑略有不同,尤其是参数号的表示方式。
用:
grib_ls -p editionNumber era5_sfc_20200101.grib看editionNumber是 1 还是 2。如果是 2,Vtable 里参数号那列可能要用paramId而不是parameterNumber,具体取决于 WPS 版本。WPS 4.x 之后对 GRIB2 的支持好了很多,但老版本 WPS 3.x 处理 GRIB2 的 ECMWF 数据经常出幺蛾子。
注意:如果你用的是 WPS 3.9 以前的版本,强烈建议先把 ECMWF 数据转成 GRIB1 再 ungrib,或者直接升级 WPS。我早期用 WPS 3.8 处理 ERA5 GRIB2,ungrib 直接段错误,折腾了两天才发现是版本问题。
3. 手写 ECMWF 海温 Vtable 的完整过程
3.1 从 Vtable.ECMWF 改起,不要从零写
WPS 的ungrib/Variable_Tables/目录下有一堆现成 Vtable,其中Vtable.ECMWF是最接近的起点。直接复制一份:
cd WPS/ungrib/Variable_Tables cp Vtable.ECMWF Vtable.ECMWF_SST然后打开Vtable.ECMWF_SST,找到 SST 相关的那行。如果原文件里没有 SST,就手动加一行。ECMWF 的 SST 映射大概是这样:
SST | 34 | sst | surface | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0注意第 4 列我写的是surface,这是根据我实际 dump 出来的 levelType 填的。如果你的数据里显示的是sfc,就改成sfc。
3.2 参数号、短名、层次类型三者必须自洽
这是最容易出错的地方。Vtable 的匹配逻辑是:ungrib 读 GRIB 文件里每个字段的元数据,然后拿这些元数据去 Vtable 里逐行比对。比对的时候,参数号、短名、层次类型三个都要对上,只要有一个对不上,这行就匹配失败。
我遇到过一种情况:参数号写对了(34),短名写对了(sst),但层次类型写的是Surface(大写 S),而实际数据里是surface(小写 s)。ungrib 是大小写敏感的,结果就是匹配不上。日志里会显示类似:
SST not found in GRIB file但你去grib_ls里看,SST 明明在。这种问题最坑,因为看起来什么都对,就是差一个字母的大小写。
提示:写 Vtable 的时候,第 4 列建议直接从
grib_ls的输出里复制粘贴,不要手打。手打很容易把surface打成Surface或者surf。
3.3 处理 ECMWF 特有的层次类型命名
ECMWF 的层次类型命名跟 NCEP 不太一样。NCEP 里表面字段的层次类型通常是surface,但 ECMWF 有时候用sfc,有时候用surface,取决于数据集和 GRIB 版本。更麻烦的是,有些 ECMWF 数据集里海温挂在meanSea或者别的类型下。
如果你 dump 出来的 levelType 是sfc,Vtable 第 4 列就写sfc。如果是surface,就写surface。不要试图用通配符,ungrib 不支持。
还有一种情况:ECMWF 的 SST 可能有两个层次值,一个是 0,一个是 1。这时候你要确认哪个是真正的海温。一般来说 level 0 是分析场,level 1 可能是预报场或者别的。Vtable 里第 5 列填对应的 level 值。
3.4 完整 Vtable 示例与逐行注释
下面是我自己用的Vtable.ECMWF_SST的核心部分,只列了海温相关的几行,实际文件里还有温度、风、气压等:
! 注释行以感叹号开头 ! WRF中间名 | 参数号 | 短名 | 层次类型 | 层次值 | ... SST | 34 | sst | surface | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 T2 | 167 | t2m | surface | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 U10 | 165 | u10 | surface | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 V10 | 166 | v10 | surface | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 PSFC| 151 | msl | surface | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0每一行的第 1 列是 WRF 中间名,这个不能乱改。SST就是SST,T2就是T2,metgrid 认的就是这些名字。第 2 列参数号和第 3 列短名从grib_ls输出里抄。第 4 列层次类型也是从grib_ls输出里抄。
注意:
PSFC那行我映射的是msl(海平面气压),这是 ECMWF 里最接近地面气压的字段。严格来说msl不是PSFC,但在很多区域模拟里够用。如果你要做高精度气压场,得用sp(surface pressure),参数号 134。
4. 跑 ungrib 并验证 SST 是否真的进来了
4.1 链接 Vtable 和 GRIB 数据
Vtable 写好后,在 WPS 目录下操作:
cd WPS ln -sf ungrib/Variable_Tables/Vtable.ECMWF_SST Vtable ./link_grib.csh /path/to/era5_sfc_*.griblink_grib.csh会在当前目录生成一堆GRIBFILE.AAA、GRIBFILE.AAB之类的软链接。确认链接数量跟你下载的 GRIB 文件数量一致。
然后编辑namelist.wps,重点改这几项:
&share wrf_core = 'ARW', max_dom = 1, start_date = '2020-01-01_00:00:00', end_date = '2020-01-02_00:00:00', interval_seconds = 21600, / &ungrib out_format = 'WPS', prefix = 'FILE', /interval_seconds要跟你数据的时间步长一致。ERA5 如果是 6 小时一次,就填 21600。
4.2 跑 ungrib 并盯住日志
./ungrib.exe >& ungrib.log跑完之后,第一件事是看日志里有没有 SST 相关的报错。用:
grep -i sst ungrib.log如果看到类似:
SST not found in GRIB file说明 Vtable 没匹配上。如果看到:
SST found in GRIB file或者没有报错,那大概率是进去了。
更稳妥的办法是直接看输出文件。ungrib 生成的是FILE:2020-01-01_00这样的中间文件,用rd_intermediate.exe查看内容:
./util/rd_intermediate.exe FILE:2020-01-01_00输出里会列出所有字段。找SST,看它的层次、时间、数值范围。如果 SST 的数值范围是 270 到 310 开尔文左右,那基本正常。如果全是 0 或者 9.99e30 这种填充值,说明数据没进来。
4.3 用 metgrid 输出反查 SST
ungrib 跑完只是第一步,真正要确认 SST 进了 WRF 的输入,得看 metgrid 的输出。跑完 metgrid 后,用 ncdump 看met_em.d01.2020-01-01_00:00:00.nc:
ncdump -h met_em.d01.2020-01-01_00:00:00.nc | grep -i sst如果看到float SST(Time, south_north, west_east)这样的变量,说明 SST 已经写进去了。再用:
ncdump -v SST met_em.d01.2020-01-01_00:00:00.nc | head -50看具体数值。如果数值在 270 到 310 之间,且空间分布合理(赤道暖、高纬冷),那就没问题。如果全是 0 或者某个常数,说明 ungrib 阶段 SST 就没进来,或者 metgrid 的&metgrid里fg_name没配对。
提示:metgrid 的
namelist.wps里&metgrid段的fg_name要跟 ungrib 的prefix一致。如果 ungrib 用的是FILE,metgrid 里也要写FILE。
5. 几个我踩过不止一次的坑
5.1 大小写和空格:最不起眼但最致命
Vtable 里第 4 列surface写成Surface,或者多了一个空格,ungrib 就匹配不上。这种问题在日志里不会明确告诉你"大小写错了",只会说not found。我一开始以为是数据问题,重新下载了两遍 ERA5,最后才发现是 Vtable 里一个字母的大小写。
解决办法:写 Vtable 的时候,第 2、3、4 列全部从grib_ls输出里复制,不要手打。复制完之后用cat -A Vtable看一下有没有多余的空格或者制表符。
5.2 时间步长不一致导致 SST 错位
ERA5 的 SST 在有些年份是 6 小时一次,有些是 1 小时一次。如果你下载的时候没注意,大气场是 1 小时一次,SST 是 6 小时一次,ungrib 按时间合并的时候,SST 会被插值或者错位。结果就是某些时刻的 SST 是空的,metgrid 里 SST 出现缺测。
解决办法:下载阶段就统一时间分辨率。如果已经下载了不一致的数据,用 CDO 或者 eccodes 做时间插值,把 SST 插到跟大气场一样的时间步长。
cdo inttime,2020-01-01,00:00:00,1hour era5_sst_6h.grib era5_sst_1h.grib5.3 WPS 版本对 ECMWF GRIB2 的支持差异
WPS 3.9 之前的版本处理 ECMWF GRIB2 数据经常出问题,尤其是参数号匹配逻辑跟 GRIB1 不一样。我早期用 WPS 3.8 跑 ERA5 GRIB2,ungrib 直接段错误,日志里连报错都没有。后来升级到 WPS 4.0,同样数据同样 Vtable,一次跑通。
如果你现在还在用 WPS 3.x,建议要么升级,要么把 ECMWF 数据转成 GRIB1。转 GRIB1 用 eccodes 的grib_set:
grib_set -s editionNumber=1 input.grib2 output.grib1但注意,GRIB2 转 GRIB1 可能会丢失一些元数据,转完之后要重新用grib_ls确认 SST 的短名和层次类型没变。
5.4 metgrid 里 SST 被覆盖或者忽略
有时候 ungrib 里 SST 明明有,但 metgrid 输出里 SST 全是 0。这种情况通常是namelist.wps里&metgrid段的constants_name或者fg_name配置有问题。比如你同时链接了一个SST的静态文件和一个FILE前缀的动态文件,metgrid 可能会用静态文件覆盖动态的 SST。
解决办法:检查&metgrid段,确保fg_name只包含你 ungrib 生成的前缀,不要混入其他来源的 SST 文件。如果确实需要静态 SST,用constants_name单独指定,并且确认它的优先级。
6. 进阶:让 SST 在 WRF 里真正起作用
6.1 确认 WRF 的 namelist.input 里开了 SST
SST 进了 met_em 不代表 WRF 会用。namelist.input里&physics段有几个开关跟 SST 相关:
&physics sst_update = 1, /sst_update = 1表示 WRF 会从输入文件里读 SST 并随时间更新。如果你设成 0,WRF 会用默认的 SST 或者忽略输入里的 SST。
另外,&time_control里auxinput4_inname和auxinput4_interval也要配对,这是 SST 更新的辅助输入流。具体配置取决于你用的是哪种 SST 更新方式。
6.2 海温数据的空间分辨率匹配问题
ERA5 的 SST 分辨率是 0.25 度,而你的 WRF 域可能是 3 公里甚至 1 公里。metgrid 会把 0.25 度的 SST 插值到你的细网格上,但插值方式会影响沿海地区的模拟效果。默认的插值可能是双线性,在海岸线附近会出现 SST 平滑过度,导致海陆温差偏小。
如果你做的是沿海区域模拟,建议在 metgrid 里把 SST 的插值方式改成最近邻或者更保守的方案。具体在namelist.wps的&metgrid段里用interp_option指定:
&metgrid interp_option = 'nearest_neighbor', /但注意,全局改成最近邻会影响所有变量,最好只对 SST 单独设置。WPS 支持按变量指定插值方式,具体语法参考 WPS 用户手册。
6.3 验证 SST 是否真的影响了模拟结果
跑完 WRF 之后,怎么知道 SST 真的起作用了?最直接的办法是对比两次模拟:一次开sst_update = 1,一次关掉,看沿海站点的 2 米温度差异。如果差异明显(比如超过 1 度),说明 SST 在起作用。
另一个办法是看 WRF 输出里的SST变量。用 ncdump 看wrfout_d01_*:
ncdump -v SST wrfout_d01_2020-01-01_00:00:00 | head -30如果 SST 的空间分布跟你输入的 ERA5 SST 一致,且随时间变化,说明更新机制在工作。
提示:WRF 输出里的 SST 变量名可能是
SST,也可能是TSK(地表温度)。TSK在海面上就等于 SST,在陆地上是地表温度。看的时候注意区分。
7. 我个人的几个习惯和最后的小技巧
折腾 ECMWF 海温数据这些年,我养成了几个习惯,分享出来可能对你有用。
第一个习惯:每次写新 Vtable 之前,先花五分钟把 GRIB 文件的字段清单 dump 出来存成文本。命令是grib_ls -p shortName,paramId,levelType,level,dataDate,dataTime *.grib > grib_fields.txt。这样写 Vtable 的时候直接查这个文本,不用反复跑 grib_ls。而且以后换数据集,对比一下字段清单就知道哪里变了。
第二个习惯:ungrib 跑完之后,先别急着跑 metgrid,先用 rd_intermediate 看一眼 SST 在不在。这一步花不了一分钟,但能省掉后面 metgrid 跑完发现 SST 缺失再回头查的半小时。rd_intermediate 的输出里,SST 的数值范围、层次、时间都一目了然。
第三个习惯:Vtable 里每一行都加注释。比如:
SST | 34 | sst | surface | 0 | ... ! ERA5 sea surface temperature, paramId 34这样过几个月再回来看,或者把 Vtable 分享给别人的时候,一眼就知道这行是干什么的。ECMWF 的参数号不是每个人都记得住,注释能省很多沟通成本。
最后一个小技巧:如果你经常需要在不同数据源之间切换,可以写一个简单的 shell 脚本,自动根据 GRIB 文件的editionNumber和centre选择对应的 Vtable。比如:
#!/bin/bash centre=$(grib_ls -p centre $1 | tail -1 | awk '{print $2}') if [ "$centre" = "ecmf" ]; then ln -sf ungrib/Variable_Tables/Vtable.ECMWF_SST Vtable elif [ "$centre" = "kwbc" ]; then ln -sf ungrib/Variable_Tables/Vtable.GFS Vtable fi这样每次换数据源不用手动改 Vtable 链接,少一步操作少一个出错的机会。ECMWF 的 centre 代码是ecmf,NCEP 是kwbc,这个在grib_ls输出里能看到。
海温这东西在区域模拟里影响很大,尤其是沿海和岛屿区域。Vtable 写对了,SST 进来了,后面的模拟才有意义。如果 SST 没进来,WRF 会用默认值或者气候态,跑出来的结果在沿海地区基本没法用。所以这一步值得多花点时间确认清楚。