做中尺度气象模拟的人,多多少少都有过这样的冲动:把一个真实台风或者暴雨过程装进自己的电脑,然后慢慢调整地形、土地利用、物理方案,看老天爷会不会“配合”。WRF就是满足这种冲动的核心工具。这篇文章不是理论课,而是按我实际跑通一套完整模拟的路径来写的:从编译WRF开始,到用GFS与ERA5准备驱动场,运行台风暴雨个例,再到修改土地利用和地形数据,设计敏感性试验,最后用Python完成专业分析和绘图。整个过程覆盖了搭建本地“天气实验室”的主要技术点,适合刚开始接触WRF、或者已经能跑通但想做过程研究和敏感性试验的气象相关专业学生、科研人员和业余爱好者。
1. 把“天气实验室”搭起来:硬件选型、软件依赖与WRF编译实录
1.1 机器配置别只看核心数,内存和磁盘才是隐藏瓶颈
很多新手会先问到底需要多高配置的机器。我的观点很直接:一开始不必追求服务器级别,但要清楚瓶颈在哪里。模拟计算确实靠CPU,WRF用MPI并行,核心越多,跑得越快,可是大部分卡脖子的地方反而不在CPU。
以一次三层嵌套台风模拟为例,外层d01是27 km,中间d02是9 km,内层d03是3 km,网格点加起来几百万,如果每6分钟输出一次三维场,单是wrfout就能堆出几十个GB。我见过不少人在d03跑到一半的时候磁盘写满,导致结果前面全浪费。所以第一优先级是存储,至少预留500 GB空闲空间,再考虑CPU核心数和内存。内存方面,常见的三层嵌套配置建议32 GB起步,如果你想把两层都设成1 km以内的高分辨率,那64 GB会更从容。
另外要注意温度控制,很多工作站跑长时间模拟时因为散热压不住出现节点直接卡死。我自己的习惯是,正式跑之前先用一个60小时的小案例连续运行测试,观察CPU温度和内存占用,确认稳定再上正式任务。这样做看起来慢,其实省时间。
1.2 Linux环境与依赖库:最省事的组合方式
WRF原生支持Linux和macOS,Windows需要借助WSL或者虚拟机,但我不推荐在Windows上折腾,问题太多。选一个长期支持版Linux发行版,例如Ubuntu 20.04/22.04,可以少踩很多坑。
编译WRF之前,需要准备编译器、MPI库和NetCDF相关库。最常见的一套组合是:
- gcc、gfortran、g++ 编译器
- mpich 或 openmpi 并行库
- zlib、libpng、jasper 用于GRIB数据解码
- HDF5、netCDF-C、netCDF-Fortran
这里特别提醒一下,WRF对netCDF版本比较敏感,尤其是netCDF-Fortran和netCDF-C版本之间要保持兼容。我在Ubuntu上直接apt安装时遇到过版本错配,计算过程中metgrid和real.exe能跑,但wrf.exe在初始化阶段就报错。后来全部手动编译,或者直接用conda管理的环境,问题才消失。
如果不想在系统库里折腾,可以用conda创建独立环境来安装编译器以外的东西。实际操作是这样的:
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh bash Miniconda3-latest-Linux-x86_64.sh conda create -n wrf_env python=3.9 conda activate wrf_env conda install -c conda-forge netcdf-fortran netcdf-c hdf5 mpich jasper libpng zlib然后把环境路径写入环境的变量。编译WRF前,把这些变量导出:
export NETCDF=$CONDA_PREFIX export HDF5=$CONDA_PREFIX export WRF_DIR=$HOME/WRF export PATH=$CONDA_PREFIX/bin:$PATH export LD_LIBRARY_PATH=$CONDA_PREFIX/lib:$LD_LIBRARY_PATH用conda环境的好处是库之间版本配套,基本不会出现依赖缺漏的问题。缺点是增加一层文件系统开销,对大规模并行来说效率略低,但对学习和单机工作完全够用。
1.3 WRF 4.x的编译流程与常见坑
从GitHub或者官网下载WRF源码后,进入目录执行:
./configureconfigure会列出很多选项,一般x86_64 Linux配gfortran和mpich或openmpi,选带dmpar的那一项,也就是分布式内存并行。如果找不到合适的选项,说明依赖库环境变量没设好,回头检查NETCDF路径。
配置完成后执行:
./compile em_real这个编译过程比较久,几十分钟到一两个小时都正常。编译结束后,检查main目录下是否生成了wrf.exe、real.exe和ndown.exe。只编译成功但没有这三个文件,等于没成功。
我遇到过两个比较典型的编译错误。第一个是cpp: error: unrecognized command line option,这通常是编译器版本和WRF版本不匹配导致,换老版本编译器或者升级WRF。第二个是netCDF路径没找到,在configure时提示NetCDF not found,此时确认$NETCDF是否指向conda环境根目录。还有一个容易被忽略的是,若系统里同时存在多个netcdf,configure找到的路径和编译器实际链接的路径不一致,编译时就会报各种看不懂的符号错误。
编译通过之后,我建议先跑一下官方自带的理想化案例,确认wrf.exe能正常启动,再去处理真实资料的流程。这一步跳过,后面出了问题会很难区分是数据库问题还是模式本身问题。
2. 驱动场从官网到本地:GFS与ERA5的下载策略和WPS预处理
2.1 选GFS还是ERA5:实时预报 vs 再分析资料
WRF模拟不能用模型自己凭空起报,必须由外部的大尺度数据提供初始场和边界场。目前最常用的就是GFS和ERA5。
GFS是美国国家环境预报中心发布的全球预报数据,时效强、更新快,每天多次发布,水平分辨率大约0.25度,时间间隔有1小时、3小时和6小时。如果你关心的是当前正在发生的天气过程,或者想做一个“准业务化”的预报试验,GFS最合适。
ERA5是欧洲中期天气预报中心的第五代再分析资料,把历史观测和模式输出融合起来,生成了一套时间连续、变量完整的数据集,水平分辨率约0.25度,时间间隔为1小时。ERA5的优点是稳定、完整、适合科研统计,不会因为预报时效导致模拟初期就引入较大偏差。缺点是发布时间有滞后,通常需要几天到几个月,不适合实时个例。做典型台风暴雨过程复盘和敏感性试验,ERA5是更稳妥的选择。
两者下载方式有很大差别。GFS可以走科研数据服务网站或云存储,用wget直接批量拉取;ERA5需要注册账号,拿到API密钥后通过Python的cdsapi下载。
2.2 GFS首发数据的批量下载脚本
GFS文件按时间路径存储,以2023年某台风过程为例,假设选择2023年7月21日00时作为起报时刻,需要下载f000到f120的多个时次。下载命令可以写成:
#!/bin/bash yy=2023; mm=07; dd=21; hh=00 fhr=0 while [ $fhr -le 120 ]; do fhr3=$(printf "%03d" $fhr) URL="https://nomads.ncep.noaa.gov/cgi-bin/filter_gfs_0p25.pl?file=gfs.t${hh}z.pgrb2.0p25.f${fhr3}&lev_400_mb=on&lev_500_mb=on&lev_850_mb=on&var_TMP=on&var_UGRD=on&var_VGRD=on&var_HGT=on&var_RH=on&subregion=&toplat=40&leftlon=110&rightlon=130&bottomlat=20&dir=%2Fgfs.${yy}${mm}${dd}%2F${hh}%2Fatmos" wget -O gfs_${fhr3}.grb2 "$URL" fhr=$(($fhr + 6)) done需要注意的是,下载时最好只选择WRF运行区域范围内的子区域和必要高度层,既能降低下载时间,也能减少后续WPS处理的数据量。因为WRF只需要外围区域的边界强迫,不需要全球完整数据。当然如果你希望稳妥一点,也可以直接下载全球数据,代价是磁盘占用和处理时间都会明显增加。
2.3 ERA5通过CDS API下载:
ERA5下载最关键的是变量列表要完整。对WRF驱动来说,核心变量包括:
- 气压层上的位势、温度、风场U/V、相对湿度或比湿
- 地面气压、2米温度、2米露点温度、10米风场
- 海表温度、海平面气压
- 土壤温度和土壤湿度(看陆面方案是否需要)
如果缺了任何一个关键变量,ungrib阶段会报错,或者生成的中间文件变量不完整,后面metgrid会出现数据空洞。
在CDS官网提交请求或用Python脚本批量下载时,建议把时间段按小时拆开,每次请求尽量控制在合理范围,以免请求排队时间过长。
import cdsapi c = cdsapi.Client(url="https://cds.climate.copernicus.eu/api", key="你的KEY") c.retrieve( "reanalysis-era5-pressure-levels", { "variable": ["geopotential", "temperature", "u_component_of_wind", "v_component_of_wind", "relative_humidity"], "pressure_level": [1000, 975, 950, 925, 900, 850, 800, 700, 600, 500, 400, 300, 250, 200], "product_type": "reanalysis", "data_format": "grib", "year": "2023", "month": "07", "day": ["20", "21", "22", "23"], "time": "00:00", }, "era5_pressure_202307.grib")下载完成后,务必检查文件大小和时间步数,避免某个时次数据缺失。一个常见问题是:ERA5的netCDF格式下载到本地后变量名和GRIB2不一样,处理方式也不同。我建议统一下载GRIB格式,与GFS一起走 ungrib 流程,省去变量名转换的麻烦。
2.4 WPS链条:geogrid、ungrib、metgrid
WPS负责把外部数据“翻译”成WRF认识的格式。完整流程是:
- geogrid定义模拟区域和静态地理数据
- ungrib把GRIB/GRIB2数据解码成中间格式
- metgrid把中间格式数据插值到geogrid定义的网格上
运行ungrib前,需要把下载的GRIB文件链接到WPS目录,并把相应的Vtable复制过来。GFS和ERA5使用的Vtable不同,GFS一般用Vtable.GFS,ERA5因为也以GRIB形式提供,用Vtable.ERA5或Vtable.ECMWF都行,关键是变量匹配。别把Vtable搞错,否则ungrib出来的中间文件会缺变量。
常见报错是“Unknown record”或者“Level not found”,这往往说明数据文件缺少特定层次,或者Vtable指向了错误的变量。还有一次我遇到metgrid输出场为零,排查半天发现是ungrib阶段中间文件的日期标记出了问题,我当时手动修改了link脚本里的小时计数,补上后发现是脚本时区理解错了。
从实际经验看,WPS链条里最能避免坑的做法是:一次处理一个时次的数据,先跑通再放开批量,并且每个步骤都看log文件尾部确认成功。跑批处理看似方便,一旦中间某个时次数据有问题,整个链条会卡在相同位置,排查起来反而更慢。
3. 台风暴雨个例:从namelist到输出判读的完整闭环
3.1 选个例不是越“极端”越好
让WRF跑出漂亮结果的前提,是选一个典型案例,资料完整、天气系统相对清晰、模式能正确重现冷暖空气交汇或涡旋结构。很多初学者一上来就选最强台风,结果模式积分几小时就出现CFL报错,原因可能是驱动场太复杂、地形剧烈、积云参数化不合适。建议先选择熟悉的、过程相对平稳但降水量明显的暴雨案例,等模式跑顺,再挑战台风过程。
我选择台风个例时通常会看一下路径是否经过资料密集区域,比如沿海探空站和雷达覆盖比较好的区域,方便后面用观测数据做对比验证。时间窗口建议覆盖登陆前24小时到登陆后24小时,既能看到台风的涡旋结构演变,也能分析降水分布的时空变化。
3.2 namelist.wps里的区域设计
区域设计对手是模拟成败的关键。以三层嵌套为例:
&geogrid parent_grid_ratio = 1,3,3 i_parent_start = 1, 40, 60 j_parent_start = 1, 40, 60 e_we = 200, 301, 301 e_sn = 180, 301, 301 dx = 27000, 9000, 3000 dy = 27000, 9000, 3000 /这里需要解释一下:外层d01要覆盖台风外围大尺度环境,范围不能太小,否则边界强迫和内部系统发展会不协调。d02负责覆盖台风主体云系,d03则聚焦重点降水区或地形关键区。如果d03范围太小,台风中心稍微偏离预定路径,系统就会跑到模拟区域外,结果就废了。所以d03中心,我的经验是放到台风登陆点附近略靠海洋一侧,而不是路径中心。
namelist.wps的&ungrib和&metgrid两个部分比较固定,核心是把interval_seconds设置成与驱动数据的时间间隔一致。GFS六小时一次就设21600秒,ERA5一小时一次就设3600秒。
3.3 namelist.input物理方案配置思路
namelist.input里最让人纠结的是物理方案选择,因为它决定模拟的云和降水特征。对台风案例,我的常用组合是:
- 微物理方案:WSM6或Thompson,两者对台风暖云和冷云过程都有较好表现。d01也可以考虑新Thompson,高分辨率内层更细腻。
- 积云参数化:d01必须开,d02视分辨率,3km以内可以不开。台风模拟d01常用Kain-Fritsch或Grell-Freitas。
- 边界层方案:YSU适合中尺度模拟,MYNN在高分辨率下的边界层发展细节更好。
- 陆面过程:Noah LSM经典稳定,Noah-MP可选但计算量大。
- 辐射方案:RRTMG在长波和短波上都比较好,且支持嵌套域并行。
时间步长不能拍脑袋选。经验公式大约是6*dx(km),27km对应约162秒,9km对应约54秒,3km对应约18秒。考虑到台风强对流环境,我通常会再乘0.5,即9km用30秒,3km用10秒。时间步长过大容易CFL报错,其实调小之后很多问题迎刃而解。
3.4 运行流程:从real.exe到wrf.exe的监控要点
real.exe是连接metgrid输出到初始场和边界场的过程。运行real之前需要确保metgrid输出时间从模拟起始时刻开始,一直延续到终止时刻。real.exe会读取wrfinput和wrfbdy,如果时间窗口不匹配,会提示找不到数据。我习惯在运行real前用ncdump快速检查一下metgrid的时间维度。
wrf.exe运行过程中,日志文件是rsl.out.0000和rsl.error.0000。很多人觉得看日志很麻烦,但实际上大多数错误在最后几十行里就能定位。
常见错误包括:
- CFL violation,通常出现在强对流回波与地形相互作用的地方。先调小时步长,再考虑降低内层分辨率或调整微物理方案。
- 土壤温度初始化报错,大多是era5或gfs驱动场缺少深层土壤温度层次,需要检查ungrib阶段是否包含土壤变量。
- 内存不足直接卡死或报segmentation fault,多半是嵌套域网格数太大,超出了机器内存。
模拟运行后,先不要急着深入分析。看一下2米温度、海平面气压的基本时空分布,对比前几个积分的合理性。比如台风中心气压有没有随时间明显下降,云系有没有沿路径移动。如果模拟三小时就出现气压剧烈震荡,多半是初始场和模式不够协调,先重查namelist。
判断拟结果是否合理,我会用两个快速方法:一是画海平面气压场和850 hPa风场,看有没有清晰的涡旋结构和闭合低压中心;二是画3小时累积降水,看雨带是否和台风螺旋雨带位置接近。确认这两点之后,再进入后面更精细的分析。
4. 修改土地利用与地形:给模式“动手术”的具体操作
4.1 为什么要修改土地利用和地形
经典数值模拟通常直接使用自带静态地理数据,但真实世界的下垫面一直在变化,尤其是城市化扩张、农田灌溉、水库建设、林草地退化。这些变化会影响地表能量平衡和低层风场,进而改变降水分布。你如果想研究“如果这个区域变成城市,台风降水会不会增强”,那就必须在模拟中人为修改土地利用类型,再对比控制试验和敏感试验的差异。
地形修改也有类似逻辑,比如你想评估风电场对局地风场的影响,或者填海造陆对台风登陆降水的影响,就需要用新的地形高程替换原来的地形数据。这类研究在数值模拟里非常常见,也是WRF灵敏度试验的核心操作之一。
4.2 理解geo_em.d01.nc的关键变量
进行修改之前,你需要知道WRF的静态地理数据如何组织。geogrid步骤生成geo_em.d01.nc,里面包含一系列二维和三维变量。最重要的是下面几个:
- LU_INDEX:每个格点的主导土地利用类型索引,直接参与陆面过程计算。
- LANDUSEF:12个月的土地利用占比,维度是(月份, 类别, 纬度, 经度),每个格点各个月份不同类别的占比相加为1。
- TERRAINDATA:模式用的地形海拔高度,默认来自USGS/SRTM等数据。
- SOILTEXTURE、TOPOFRAC等次要变量,一般不用改。
注意LU_INDEX和LANDUSEF并不是独立的。当你修改LANDUSEF时,LU_INDEX必须同步更新为占比最大的类别,否则陆面过程的土地利用属性会和实际不匹配。
4.3 用Python直接修改geo_em文件
最常见的方法是修改某个区域的LANDUSEF,把原本的农田或草地类型改为城市类型。下面以USGS 24类分类为例,假设城市类别编号为1,农田类别为3,现在要把某个矩形区域改成城市。
import numpy as np from netCDF4 import Dataset src = Dataset("geo_em.d01.nc", "r+") lons = src.variables["XLONG_M"][0] lats = src.variables["XLAT_M"][0] mask = (lats > 32) & (lats < 34) & (lons > 118) & (lons < 120) landusef = src.variables["LANDUSEF"][:] # (time=1, month=12, category, lat, lon) ncat = landusef.shape[2] # 把区域范围内所有月份的土地利用改为城市类别 for m in range(12): # 先将所有类别占比清零 landusef[0, m, :, :, :] = 0.0 # 城市类别设为1,这里假设城市类别索引是1 landusef[0, m, 1, :, :][mask] = 1.0 src.variables["LANDUSEF"][:] = landusef # 重新计算LU_INDEX lu_index = src.variables["LU_INDEX"][0, :, :] for m in range(12): lu_index[mask] = 1 # 城市类别编号 src.variables["LU_INDEX"][0, :, :] = lu_index[mask] src.close()这段代码的关键点在于,修改完LANDUSEF后一定要重算LU_INDEX,而且矩形区域的经纬度范围要和geo_em里的XLONG_M、XLAT_M一致,否则修改会落在区域外。如果你想按某个GIS矢量边界来做不规则区域,可以用GDAL把矢量栅格化成与geo_em相同分辨率的掩膜数组,再对掩膜区域赋值。
4.4 地形修改与一致性检查
地形修改类似,先用外部的高分辨率DEM处理成模拟网格坐标,插值后替换TERRAINDATA。下面是一个简化思路:
- 用GDAL读取外部DEM
- 用Warp重投影到与geo_em相同的坐标系
- Resample到相同分辨率,然后写入geo_em
这个过程最需要注意的是坐标匹配。geo_em里的LAT/LON是经纬度网格中心点,外部DEM可能使用UTM或其他投影。必须先把DEM转换到WGS84经纬度,再进行双线性插值。插值完的地形要平滑处理,不然模式积分时容易出现气压场剧烈变化的数值不稳定。
我个人的建议是,如果只是简单研究,直接修改geo_em文件就够;如果研究区域地形复杂,或者涉及城市精细尺度,建议修改之后跑一个短时模拟,对比修改前后的海平面气压场和风场,确认没有出现不合理噪声。
5. 敏感性试验设计:让WRF帮你回答“如果……会怎样”
5.1 常见敏感性试验类型
敏感性试验的核心是控制变量。想研究土地利用变化的影响,保持驱动场和物理方案完全不变,只替换新土地利用数据;想研究物理过程参数化的影响,保持数据不变,只改变某一个物理方案。还有地形敏感性、海温敏感性、初始场扰动敏感性等。
这里一定要分清楚,“敏感性试验”和“普通预报试验”的区别:前者是通过对照试验来诊断模式响应的因果链条,后者是尽可能让预报接近真实。所以敏感性试验里,人为改动之后模拟结果与真实观测偏差变大,这本身不一定是坏事,反而说明该因子确实在起作用。
5.2 控制变量设计:从CTL到多组试验
举一个土地利用敏感性实验的例子。假设我想研究“城市扩张对台风暴雨的影响”,可以这样设计:
| 试验名 | 驱动场 | 物理方案 | 土地利用 | 地形 |
|---|---|---|---|---|
| CTL | ERA5 | 统一方案A | 原始土地利用 | 原始地形 |
| URB | ERA5 | 统一方案A | 城市扩张方案 | 原始地形 |
| TERR | ERA5 | 统一方案A | 原始土地利用 | 修改后地形 |
| URB+TERR | ERA5 | 统一方案A | 城市扩张方案 | 修改后地形 |
这样设计的好处是:CTL和URB对比,能单独分离出土地利用变化的影响;CTL和TERR对比,能单独分离出地形变化的影响;URB+TERR与URB对比,还能看出两个因素叠加后有没有非线性效应。
实际操作时,我通常为每个试验建立单独目录,目录里只存放链接过来的wrfout和namelist,所有试验共用同一份驱动场和初始场数据。这样能最大程度避免误操作导致试验组之间数据不一致。命名也要清晰,比如CTL、URB、TERR,不要用test1、test2这种名字,否则跑完一周你自己都分不清哪组是哪组。
5.3 如何判断敏感信号是真实的
做完敏感性试验后,不能只画两张图说“有差异”。要判断差异是不是真实的模式响应,需要做统计检验。常用方法是对多时次输出做配对t检验,比较CTL和URB在感兴趣区域的平均降水、温度或风场差异是否显著。
更具体的做法是:
- 提取每个试验的逐小时降水字段
- 选定一个固定区域,比如台风暴雨落区或城市下游区域
- 对每个时次计算区域平均,得到一组时间序列
- 用Python的scipy.stats.ttest_rel计算配对t检验
- 如果p值小于0.05,可以认为两个试验的差异在统计上显著
这里有个容易踩的坑:如果样本数太少,比如只有几个时次,差异再多也通不过显著性检验。所以设计试验时,最好把模拟时段拉长,或者用多个个例重复试验。多个个例的做法更符合真实科研逻辑,但计算量会成倍增加。
我还想强调一个职业习惯:敏感性试验里最怕无意中改变了不该变的因素。比如你在修改土地利用时不小心把海表温度覆盖了,或者运行real.exe时没有复制同一份驱动数据,都会让灵敏度信号失真。每次跑完一组试验,建议先用diff对比namelist和相关输入文件的checksum,确认除了目标变量外没有其他差异。
6. 用Python做专业级后处理分析:从读取wrfout到论文级绘图
6.1 Python环境:别手动装包,直接上conda
WRF后处理最常用的库是wrf-python、netCDF4、xarray、matplotlib、cartopy。新手最容易在装包阶段卡住,最主要原因是用pip直接装wrf-python在某些平台上很难成功。虽然现在pip装wrf-python也可以,但依赖库冲突还是很多。最省心的方法是先用conda创建环境,然后一条命令装上全部依赖:
conda create -n wrf_post python=3.9 conda activate wrf_post conda install -c conda-forge wrf-python netcdf4 xarray dask matplotlib cartopy pandas scipy如果conda下载慢,换成国内镜像源。这条命令装完之后,matplotlib、cartopy和wrf-python都能用,基本覆盖了日常绘图需求。这里特别提醒一下,wrf-python依赖的numpy版本不能太新,有些版本组合会出现无法导入的问题,建议用conda默认解析出来的版本组合,别手动升级。
6.2 读取wrfout并理解网格结构
wrfout文件本质是netCDF格式,可以用netCDF4直接读取,但直接用wrf-python的getvar更方便。下面这段代码可以快速读取台风模拟的海平面气压和10米风场:
import xarray as xr import wrf import matplotlib.pyplot as plt import cartopy.crs as ccrs import cartopy.feature as cfeature ds = xr.open_dataset("wrfout_d03_2023-07-21_00:00:00") slp = wrf.getvar(ds, "slp") ua = wrf.getvar(ds, "ua") va = wrf.getvar(ds, "va") lon = wrf.getvar(ds, "lon2d") lat = wrf.getvar(ds, "lat2d") proj = wrf.get_cartopy(slp) fig = plt.figure(figsize=(10, 8)) ax = plt.axes(projection=proj) ax.add_feature(cfeature.COASTLINE, linewidth=1) ax.add_feature(cfeature.BORDERS, linewidth=0.5) levels = list(range(960, 1020, 4)) cont = ax.contourf(lon, lat, slp, levels=levels, cmap="viridis", transform=ccrs.PlateCarree()) ax.barbs(lon[::5, ::5], lat[::5, ::5], ua[::5, ::5], va[::5, ::5], transform=ccrs.PlateCarree()) plt.colorbar(cont, ax=ax, shrink=0.8) plt.title("Sea Level Pressure and 10m Wind") plt.savefig("slp_wind_d03.png", dpi=300, bbox_inches="tight")这段代码用到了wrf-python的getvar,它内部已经做了坐标变换和单位换算,比直接读netCDF再手动处理方便得多。要画降水,用wrf.getvar(ds, "RAINC") + wrf.getvar(ds, "RAINNC"),这是累积降水,差分即可得到单位时间降水量。这里的RAINC是积云参数化降水,RAINNC是显式微物理降水,千万别漏掉任何一个。
6.3 横坐标太密集、变量名记不住怎么办
这个坑来自真实经历:很多新手用matplotlib画图时,横坐标显示时间序列,或者地图坐标轴刻度非常密集,甚至叠在一起,图像很难看。解决办法是设置合适的时间分辨率,或者用MaxNLocator控制刻度数量。地图坐标的经纬度标注也一样,手动设置间隔即可。
还有一个常见问题是wrf-python的getvar变量名不是netCDF原变量名,比如:
- 温度是
theta,画图时需要转成摄氏温度,可以用tk获取开尔文温度 - 位势高度在WRF里是
z,而不是GRIB文件里的HGT - 降水累积量是
RAINC、RAINNC,对应命名要记牢
想快速查看所有可用变量,可以执行wrf.getvar(ds, "ALL")看变量清单,或者直接打印ds.data_vars看项目。最好在项目里维护一个自己的变量映射表,方便写脚本时查。
6.4 批量绘制和统计分析
跑了一组敏感性试验,通常要生成几十上百张图。我习惯用Python写一个循环脚本,遍历多个wrfout文件,把每个时次的降水、风场、温度图批量输出。批量脚本要特别注意变量提取和保存的循环逻辑,别把时次顺序搞混。
在统计分析部分,我还常用xarray直接读取多个试验的累计降水,做差值场和区域平均:
ctl = xr.open_mfdataset("CTL/wrfout_d03_*", combine="by_coords") urb = xr.open_mfdataset("URB/wrfout_d03_*", combine="by_coords") # 计算每个试验在某个时间段内的累计降水 def total_precip(ds): return (ds["RAINC"].sum(dim="Time") + ds["RAINNC"].sum(dim="Time")).values precip_ctl = total_precip(ctl) precip_urb = total_precip(urb) diff = precip_urb - precip_ctl # 区域平均 region = (lat > 30) & (lat < 34) & (lon > 118) & (lon < 122) print("区域平均降水量差值(URB-CTL):", diff[region].mean())这段代码里需要注意的坑是xr.open_mfdataset的时间维度合并方式,如果文件名前缀不统一,或者存在重叠时次,会导致计算结果错误。稳妥起见,可以用glob.glob排序后传入,或者先合并再检查维度长度。
6.5 绘图细节:从能出图到“论文级”
能把图画出来和画出图文并茂的图之间,差距通常在细节。具体控制点包括:
- 地图投影选择:中纬度区域用Lambert Conformal,小范围区域用横轴墨卡托;WRF自带
get_cartopy函数,可以直接获得正确投影。我自己画单层嵌套时经常直接用get_cartopy输出,省去手动设置投影。 - 色标选择:降水通常用
precipitation色标,风场用矢量和填色结合,温度用红蓝渐变。用matplotlib的ColormapBuilder调整色标起点和终点,避免自动配色掩盖真实值。 - 字体设置:插入图名、标签时,统一设置
rcParams["font.sans-serif"]为支持中文的字体。如果不想为中文乱码头疼,直接用英文标签也行。 - 保存分辨率:论文投稿通常要求300 dpi以上,出图时指定
dpi=300,文件格式优先PNG或PDF,PDF体积小且缩放不变形。
绘图还能做箱线图、误差条形图、时间序列等,结合你的研究问题自由发挥。关键是形成标准化的输入-函数-输出流程,让每个试验的图风格一致,后面写论文和报告时能直接复用。
从搭建环境到修改地形,再到敏感性实验设计和Python分析,这是一条完整的WRF研究路径。我实际跑通一轮之后,最大的感受是:WRF的难点不是运行本身,而是你对数据流和物理过程的理解。每一步的取舍都会影响结果,而最好的调试工具不是教程,是你对日志和数据变量的熟悉程度。当你第四次遇到CFL报错、第五次发现GEODATA缺文件时,这些坑就会变成一个可复用的经验库。希望这份记录能让你在搭建自己的天气实验室时少走一些弯路,多一些真正理解模式的时间。