做WRF后处理这几年,我踩过的坑比跑过的case还多。尤其是从namelist配置到降水计算这一段,看起来都是“标准操作”,但只要一个参数设置不对,后面所有分析全部作废。这篇文章我把整套流程掰开揉碎讲清楚,从namelist的每项关系到降水计算的每个细节,包括那些最容易出错、最容易被忽视的隐藏坑,全部一次说完。
这篇文章适合刚接触WRF的入门者,也适合已经跑通流程但总在后处理阶段“翻车”的进阶用户。核心目标就一个:让你在看这篇文之前踩过的坑,不要再踩第二遍。
1. 先理清思路:WRF后处理到底在处理什么
1.1 WRF后处理不是“跑完就完事”的那一步
很多人有个误区,觉得WRF模拟一结束,模式输出就是“结果”了。实际上wrfout文件里的原始变量,几乎没有一个可以直接用——温度、降水、风场、位势高度,全部需要经过转换、单位修正、坐标系转换,才能变成真正有意义的气象产品。
我经常用一个类比:wrfout就是“毛坯房”,后处理是“装修”。你买房不能拎包入住,同理跑完WRF也不能直接画图。整个后处理环节,本质上是把模式网格上的物理量,翻译成人类可读、可分析、可检验的“成品”。降水计算又是其中最容易翻车的一环,因为WRF模式中的降水变量是累计量而非瞬时量,新手甚至不少老手都会在这个地方栽跟头。
1.2 一条清晰的后处理链路
先明确整条链路大概是什么样,后面才不会迷路:
- 检查namelist配置:这是后处理能否顺利进行的前提。输出频率、输出变量、输出格式,全在这两个文件里决定。
- 提取与修正变量:从wrfout中读取RAINC、RAINNC等原始变量,修正单位,处理累计量。
- 降水计算:将累计降水转换成指定时间段的降水增量,或累计过程降水总量。
- 网格到站点/区域的转换:把模式的网格降水数据插值到目标站点或流域,计算区域平均。
- 可视化与检验:绘制降水分布图,与观测数据对比验证。
每一步都有坑,而其中源头就在namelist配置。很多人后处理跑着跑着发现没数据、没变量、时间轴不对,回头一查,全是namelist里的输出设置出了问题。
2. namelist配置:后处理的“地基”全在这两个文件里
2.1 namelist.wps:模拟区域和网格分辨率决定后被分辨率
namelist.wps的主要工作是定义模拟区域、嵌套方案和静态地理数据。很多人觉得它只跟前处理(WPS)有关,跟后处理扯不上关系。但你要知道:后处理里做得最多的操作是区域提取、插值、嵌套合并,这些操作的全部前提,就是namelist.wps里设置的网格参数。
关键的几个参数:
&geogrid parent_grid_ratio = 1, 3, 3, i_parent_start = 1, 30, 45, j_parent_start = 1, 30, 45, e_we = 100, 181, 226, e_sn = 100, 181, 226, /e_we和e_sn决定了每个域的网格数,直接决定wrfout数据文件的维度大小。建议在跑之前就规划好几个域的总数据量,不然后处理阶段读文件能把内存吃爆。parent_grid_ratio是嵌套网格比例,通常取3。这个参数影响后处理时嵌套边界上的插值权重,如果你要做d01和d02的无缝合并,就得根据这个比例设置插值参数。i_parent_start和j_parent_start决定了嵌套域在父域中的起始网格位置,后面做子域截取时要用到,别丢掉。
还有更基础但极其关键的geog_data_res选项:
geog_data_res = 'max_dom', 'max_dom', 'max_dom',这个参数控制了静态地理数据的最高分辨率。很多人图省事全部用’max_dom’,结果在d01这种粗网格域上也生成了极高分辨率的地形数据,wrfout里的地形细节被强行“拉伸”到粗网格,后处理画地形跟降水叠加图时,地形边界会出现很多锯齿状伪象。我的建议是:d01用'd30s',d02和d03用'max_dom'。因为d01分辨率本身就粗,30秒的静态数据已经远远够用,还能省下不少磁盘空间和运行时间。
2.2 namelist.input中与后处理强相关的关键项
namelist.input是决定wrfout内容的核心文件,这里面的设置直接决定了你后处理阶段“有没有料可用”。
先看跟输出直接相关的这部分:
&time_control history_interval = 60, 60, 60, ! 分钟 frames_per_outfile = 1, 1, 1, io_form_history = 2, ! 2代表NetCDF auxhist10_interval = 10, 10, 10, ! 10分钟输出一次 /history_interval是历史输出频率,单位是分钟。做降水分析时,60分钟的输出基本是底线;如果你要分析短时强降水的演变过程,建议至少设成10到30分钟。频率太低,会错过降水峰值。frames_per_outfile是每个文件包含的帧数。设成1就是每个时间步一个文件,后处理时文件数量会爆炸;设成24或更高则文件少但单文件大,读取时内存压力大。结合自己的机器配置来权衡,我一般设frames_per_outfile = 1,配合文件名中的时间戳,既方便检查某一时刻的场,也方便并行处理。io_form_history = 2表示用NetCDF格式输出,这是后处理工具兼容性最好的选择。千万别用1(二进制)或3(pnetcdf,除非你确定自己会用并行读取)。
再看物理过程相关的几个选项,它们对降水变量的影响非常大:
&physics ra_lw_physics = 4, 4, 4, ra_sw_physics = 4, 4, 4, sf_sfclay_physics = 1, 1, 1, cu_physics = 1, 1, 1, ! 积云参数化 mp_physics = 8, 8, 8, ! 微物理方案 /cu_physics决定了积云参数化方案,这直接影响RAINC(对流性降水)变量。如果你设成0(关闭),那么RAINC始终为0,所有降水都归到了RAINNC。后处理时如果没意识到这一点,看到RAINC全0就以为是模式出错了,其实只是积云方案没开。mp_physics决定微物理方案,直接影响RAINNC(网格尺度降水)。不同方案对液态水、冰相过程的描述不同,降水总量和强度会有明显差异。做降水检验时,别忘了记录你用的方案版本。
还有一类容易被人忽略的设置,也在后处理中起着决定性作用:
&domains time_step = 60, dx = 27000, 9000, 3000, dy = 27000, 9000, 3000, /dx、dy和后处理密不可分。很多降水计算脚本里需要用到网格面积来计算区域平均降水,如果直接读取wrfout里的一维数组,不会自动带上网格间距信息,就得手动从namelist或者项目文件里读入dx、dy。提前把它记录好,后面能省很多事。
2.3 最容易忽略的输出变量控制
namelist.input里最让后处理头疼的部分,不是物理方案,而是你没意识到WRF默认输出里没有你要的变量。基础wrfout文件会输出温度和风场等常规量,但一些诊断量或派生量,默认不输出。
比如你要做2米温度、10米风的检验,默认的wrfout里是有T2和U10、V10的,但如果你想用比湿q2或者表面热通量,就得用auxhist系列额外添加输出帧或变量:
io_form_auxhist10 = 2 auxhist10_outname = "wrf_auxhist10_d<domain>_<date>" auxhist10_interval = 10, 10, 10, auxhist10_begin_yyyymmddhhmmss = 2000-01-24_12:00:00, auxhist10_end_yyyymmddhhmmss = 2000-01-25_12:00:00,注意,auxhist输出的是一个全新的文件系列,后处理时要单独读取。
另一个很容易踩的坑:如果你要输出一些非默认的物理量,比如云水混合比、雨水混合比,变量名不叫QRAIN而是QRAIN、QCLOUD等,这些变量位于wrfout里,但需要确认模式编译时是否打开了相关宏。比如要输出雨滴数浓度,需要在编译WRF时开启-DWRF_DFI_RADAR之类的选项。如果你发现wrfout里怎么都找不到某个变量,别急着怀疑脚本,先去检查编译选项和物理方案是否支持这个变量的输出。
3. 降水计算:从“模式输出”到“业务产品”的关键一跳
3.1 RAINC和RAINNC到底怎么用
降水计算是WRF后处理的“重头戏”。先搞清楚三个关键变量:
RAINC:积云对流降水(来自积云参数化方案),是累计量。RAINNC:网格尺度降水(来自微物理方案),也叫显式降水,是累计量。RAIN或PREC_ACC_C:部分版本有总降水量字段,但实际用法南北差异大,别依赖它,直接用前两个算更稳。
这些变量的含义是:从模式启动时刻开始至今的累计降水量。单位是毫米(mm)。
这意味着wrfout里第t时刻的RAINC + RAINNC,是你从启动到该时刻的总降水,不是这一时刻的降水。要得到 6 小时累积降水、24 小时累积降水,必须对累计量做差分:
时段降水(t1 → t2) = [RAINNC(t2) + RAINC(t2)] - [RAINNC(t1) + RAINC(t1)]很多新手先把所有时刻的降水加起来画时间序列,得到一条一直向上的曲线,还以为是“累积降水过程”——确实,那是累计量本身。但你要是拿它当“降水量随时间变化”,就完全搞错了。降水率应该是差分后再除以时间间隔,一般表示为 mm/h 或 mm/d。
3.2 降水计算实操代码:从原始变量到过程降水
我用Python + xarray做降水后处理,是目前比较高效且容易上手的方案。这里给出一段实际跑过的计算代码,你可以直接参考:
import xarray as xr import numpy as np import pandas as pd ds = xr.open_dataset('wrfout_d01_2000-01-24_12:00:00') # 假设文件里包含多个时间帧 # 读取累计降水 rainc = ds['RAINC'] # (time, south_north, west_east) rainnc = ds['RAINNC'] # 总累计降水 total_precip_cumulative = rainc + rainnc # 计算6小时累计降水(假设数据为逐小时输出) # 取第6时刻和第0时刻之间的差值 precip_6h = total_precip_cumulative[6, :, :] - total_precip_cumulative[0, :, :] # 保留有效值 precip_6h = precip_6h.where(precip_6h >= 0, 0)注意最后一行:累计量差分后理论上不会出现负值,但在嵌套边界、地形陡峭区域,有时会出现极小的负值(浮点误差或地形掩盖问题),直接用where(precip >= 0, 0)处理一遍能避免后续统计时出现诡异负数。
如果你想把网格降水插值到站点,wrf-python库提供了现成方法:
from wrf import getvar, lltoxy, interplevel # 读取目标站点的经纬度并转换为模型网格坐标 sx, sy = lltoxy(ds, station_lon, station_lat) # 使用双线性插值从网格降水获取站点降水 station_precip = float(interplevel( precip_6h, ds['HGT'], ds['HGT'].values[sy, sx] # 用站点处地形高度做垂直插值?不!这里是水平插值示例 ))这里的示例有点简化。更稳妥的插值方法是使用scipy.interpolate.RegularGridInterpolator或者metpy.interpolate里的方法,将网格点数据插值到站点坐标。核心原理是:wrfout是经纬度网格(在兰伯特投影下),直接用经纬度坐标做双线性插值即可,但要确认目标站点的经纬度在同投影下是否有效。我建议用下面这种方法,更直观:
from scipy.interpolate import RegularGridInterpolator # 提取经纬度坐标 lats = ds['XLAT'][0, :, :].values lons = ds['XLONG'][0, :, :].values # 创建插值函数(网格是规则经纬度时为真) # 注意:这里仅适用于规则网格,兰伯特投影的wrfout需做投影转换 interp_func = RegularGridInterpolator( (lats[:, 0], lons[0, :]), precip_6h.values, method='linear', bounds_error=False, fill_value=None ) station_precip = interp_func((station_lat, station_lon))如果你处理的是兰伯特投影的WRF输出,最简单的办法是用wrf-python自带的插值工具,或者用pyproj把站点经纬度投影到区域网格,再用最近邻或双线性插值。
3.3 scale_factor、单位与wrf_reduce_units的噩梦
降水计算中最隐蔽的坑是单位问题。
默认情况下,WRF输出的RAINC和RAINNC单位是毫米(mm)。但是!如果你在编译WRF时设置了简化单位选项(wrf_reduce_units = .true.),部分变量的单位会变成国际单位制,降水的单位可能不再直接是毫米,需要额外做单位换算。
我实际遇到过这样的情况:下载的某个WRF编译版本默认开启了wrf_reduce_units,输出的降水单位变成了kg/m²,而水密度是1000 kg/m³,所以1 kg/m² 相当于 0.001 mm。如果没注意,直接把数值拿来分析,算出来的降水会整整小了1000倍,画出来的图几乎看不见降水。
检查单位的方法很简单,读取wrfout里变量的units属性:
print(ds['RAINC'].attrs['units']) # 如果显示 'mm',默认没问题 # 如果显示 'kg m-2',就是可归约单位模式,需要转换所以,在计算前务必检查变量属性。如果遇到kg m-2这类单位,转换方法:
# kg/m^2 转 mm:除以水的密度 1000 kg/m^3 precip_mm = precip_kg_m2 / 1000.0严格来说,降水深度(mm)与降水质量密度(kg/m²)的关系是:单位面积上的水质量 = 水体深度 × 密度。因此转换因子是水的密度约1000 kg/m³。
3.4 降水率计算与单位换算
计算降水率时,很多人会直接把差分量除以时间间隔(小时数),得到mm/h。这样做在时间步长均匀时没毛病,但如果时间间隔不是整数小时,比如输出频率是10分钟,你要算1小时降水率,处理方式就不一样了。
正确的做法是:
小时降水率 (mm/h) = 时段累计降水 (mm) / 时间间隔 (小时)例如,10分钟输出一次,你想算10分钟降水率(mm/h),公式是:
10min降水率 = (累积降水_第10min - 累积降水_第0min) / (10/60小时)这段话文字不多,但很多人折在这里。之前有个做暴雨个例分析的朋友,输出频率是5分钟,算降水率时直接除以5,当成mm/h,数值放大了12倍。他说“这个降水率太夸张了”,一查果然是时间单位没换算好。
3.5 降水变量缺失怎么办
有时候打开wrfout发现RAINNC、RAINC都是0,或者干脆没有这些变量。常见原因有三:
- 物理方案设置问题:比如关闭了积云方案,RAINC自然为0;微物理方案不产生网格尺度降水,RAINNC为0。检查namelist.input的物理方案配置。
- 编译时功能开关问题:某些WRF版本需要开启特定的编译宏才会输出相关变量。比如你要输出雷达反射率,就得在编译时配置相关选项。
- 读取了错误的文件:wrfout文件有多个域(d01、d02、d03),不同文件里的变量不全相同,尤其auxhist系列文件中通常不会包含所有物理变量。
遇到缺失变量,先排查这几点,比自己瞎猜要快得多。
4. 常见问题与排查技巧实录
4.1 时间轴错位:wrfout里的时间到底是什么
wrfout中的时间坐标变量叫Times,它是一个字符型变量,格式是'2000-01-24_12:00:00'。但很多人容易忽略一个细节:这个时间是模型模拟时间,不是世界时(UTC),也不是当地时。它取决于你启动WRF时在start_yyyymmdd_hhmmss里设置的时间。
如果你做对比观测数据时,把wrfout的时间直接当成UTC来用,在非UTC区域的站点检验里会导致几小时的偏差。比如模拟的是中国东部(UTC+8),启动时如果要模拟北京时间06时的降水,WRF启动时间应设置成前一天22时(UTC),wrfout里的时间是UTC,不是北京时间。
还有一个坑:Times变量是字符型,很多工具默认不认,提取时间时要先转成datetime:
# 读取Times变量并转换为datetime格式 times = pd.to_datetime([str(t, encoding='utf-8') for t in ds['Times'].values])转换后就可以方便地做时间筛选,比如只取某一天的数据。
4.2 降水值“大得离谱”或“全是0”
数值异常大,多半是单位问题或差分方向搞反了。特别是你如果拿累积量直接画图,数值会随时间不断增大,看起来“暴雨成灾”,其实是累计量本身。或者差分时错把后一时刻减去前一时刻的方向写反了(比如数组索引搞错),出现瞬间负降水,再叠加绝对值的误差。
数值全是0,则重点检查:
- 模拟时段内确实没有降水(比如冬季晴空个例),这属于正常情况。
- 物理方案不产生对应降水类型(如未开启积云参数化),RAINC为0正常,关键看RAINNC。
- 你读的是wrfout的初始时刻(time=0),模拟刚开始,累计降水自然为0。要跳过前几个小时(spin-up时段)再分析。
4.3 嵌套域边界上的异常降水带
如果你跑的是双重嵌套(d01+d02),在后处理合并或对比时,会发现嵌套边界附近出现明显的降水不连续。这是WRF模式嵌套的物理属性,不是bug。
要解决这个问题,一般做法是:近边界区域,以细网格域(d02)为准;远离边界区域,用粗网格域(d01)。如果要画一张无缝衔接的降水图,可以在d02范围内直接用d02数据,在d01只用d01数据,交界处用渐变权重过渡。
我常用的一种简单而有效的方法是做一个缓冲带:在d02边界向外扩展5-10个网格的距离,此区域内采用d01和d02降水的线性加权平均,权重由距离边界的远近决定。
4.4 处理速度太慢:wrfout文件大得吓人
降水后处理经常涉及几十GB的wrfout文件,处理慢是常态。几个实际可行的优化手段:
- 按需提取变量:读文件时用xarray的
sel或isel只读取需要时刻和变量,别一股脑load全部数据。 - 并行处理:如果文件是一帧一个文件(
frames_per_outfile=1),可以用multiprocessing或concurrent.futures并行读取。不同时间的文件互不依赖,天然可以并行。 - 预处理降采样:如果不需要原始网格分辨率,可以用
xarray的coarsen方法对wrfout做降采样,比如把9km网格降到27km,速度能提升很多。 - 用CDO做快速处理:CDO(Climate Data Operations)处理NetCDF文件非常快,适合做区域平均、时间平均等简单操作。比如:
cdo selname,RAINC,RAINNC wrfout_d01_2000-01-24_12:00:00 precip_raw.nc cdo timcumsum precip_raw.nc precip_cum.nc # 沿时间累积不过CDO自带的时间累积函数只能做简单累积,做差分还是要用Python/NCL。
4.5 区域平均降水怎么算才准
算流域平均降水或区域平均降水,是降水后处理的高频需求。常见错误是把所有格点的降水直接平均。但WRF网格在高纬度地区,尤其是兰伯特投影下,网格面积不均匀,直接平均会导致偏差。
更严谨的做法:区域平均降水 = 区域降水总量 / 区域实际面积。即:
平均降水 (mm) = Σ(每个格点降水 × 网格面积) / Σ(网格面积)网格面积可以用WRF输出的cell_area变量(部分版本有),或者自己用投影参数计算。不方便的话,可以用wrf-python里的wrf.grid_area计算:
from wrf import grid_area area = grid_area(ds) # 返回2D网格面积数组 mean_precip = (precip_6h * area).sum() / area.sum()这个方法算出来的区域平均才是物理上正确的。
5. 我的实操心得与避坑清单
说实话,WRF后处理没有太深奥的算法难点,真正折磨人的全是细节。这里再重点重复几个我觉得最关键的经验:
第一,namelist的配置永远要留档。每次跑完case,第一时间把namelist.wps和namelist.input复制一份,与wrfout放在同一目录下。这对事后回溯模拟结果、排查变量异常、以及与他人协作都极其重要。很多次我看到别人发来wrfout文件求助,我第一句就问:“namelist呢?”没有namelist,很多变量异常根本无从判断。
第二,降水计算先画时间序列再画空间分布。动手做整段模拟的降水分析前,先挑一个点(或一个区域平均)画出完整时间序列,看看曲线的形态是否符合常识。累计量应该单调递增,差分后应该出现降水事件对应的大值区间。这样能快速暴露单位错误、差分方向错误等低级问题。
第三,单位不是“绝对可信”的。不同版本的WRF、不同的编译选项、不同的物理方案,都有可能让变量的单位变得不一样。永远不要假设,打开文件看units属性,写上脚本前先在笔记本上手动算一个点的值验证。
第四,留好“独立验证”这一步。算完降水后,我会用NCL或Python独立地再算一遍关键时段的降水,与主脚本的结果对比。不是重复劳动,而是防止同一套代码里藏着的系统性bug。有一次我就是这样发现WRF的RAINC变量在处理时被脚本中某个隐藏的fillna(0)给全部变成了0——这种错误如果不是比对根本查不出来。
最后,再分享一个小技巧:wrfout的处理代码一定要面向多case复用。每次跑新的case,只需要改namelist文件路径、变量表和时间段,代码主体不用动。为了实现这一点,我建议把你的后处理脚本写成配置驱动式的,不要硬编码变量名和时间点。慢慢地你会积累出一套属于自己的“后处理工具箱”,以后再做类似分析就是复制改配置的几分钟的事。
WRF后处理这条路,看起来是模式模拟的“下游”,但实际上它决定着你模拟结果能不能真正产出价值。namelist配置和后处理降水计算是最容易出问题的两个环节,也是直接决定最终产品质量的关键所在。希望这篇文章能帮你少踩几个坑,把更多时间花在气象本身上面。