做空气质量模拟的人应该都干过这件事:把全球化学模式的输出结果塞进WRF-Chem里当初始场和边界场。早些年大家满世界找MOZART的nc文件,后来慢慢有人开始用CAMS(哥白尼大气监测服务)的再分析数据。CAMS数据覆盖面广、化学物种相对齐全,而且在国内外的发文章场景里认可度都还不错。可问题是,这数据不是拿来就能喂给WRF-Chem的,格式、网格、坐标系、变量定义全都不一样,硬塞肯定报错。
CAMStoWRF这个工具就是为了解决这件事出现的。它负责把CAMS的GRIB/NetCDF数据读取出来,重映射到WRF-Chem的模拟网格上,再写进wrfinput和wrfbdy文件里。整个过程听着不复杂,真做起来坑很多。我前前后后用过好几遍这工具,从编译到跑通再到结果验证,每个环节都有值得记录的细节。这篇就围绕CAMS数据下载、CAMStoWRF配置、运行与踩坑这四块内容,把整个流程掰开揉碎讲一遍。
1. 项目思路拆解:CAMS数据如何一步步变成WRF-Chem初边界场
先说清楚一个最基础的问题:为什么大家宁可折腾CAMS,也不继续用传统的MOZART输出。这里头有两层原因。一是MOZART的那套全球输出数据,用起来常会遇到时间上不对应的问题,找个合适时刻的化学场得翻半天;二是CAMS本质上是同化产品,结合了卫星、地面观测和模式模拟,做出来的化学场更接近真实大气的状态。对于局地重污染过程模拟来说,初始场和边界场贴近实况能让模式少走很多弯路,也更有利于分析污染来源和演变机理。
但CAMS不能直接被WRF-Chem使用,中间有三个大坑。
1.1 CAMS资料为什么适合做化学初边界场
CAMS全球大气成分预报产品的时空分辨率都很能打。空间分辨率大约0.25度,时间上基本是三小时一组输出,足够覆盖大部分中尺度模拟的需求。更重要的是它包含的化学变量相当全,既有O3、NO2、CO、SO2这类常规痕量气体,也有有机碳、黑碳、海盐、粉尘、硫酸盐等气溶胶组分,还有多种VOCs前体物。很多做对流层臭氧研究和PM2.5来源解析的工作组,都偏好用这套数据搭初边界场。
从实用角度看,CAMS也在持续更新,还提供了一致性较好的再分析版本的整个长序列。做历史个例反演时,可以快速拉到对应时段的全球化学场,不用像以前那样到处找替代数据。这种便利性,直接决定了它在一个模拟项目中能不能成为“默认选择”。我在实际项目里对比过,用CAMS做初始场之后,模式前几个小时的O3和PM2.5浓度明显比用默认profile边界场更贴近观测,模式spin-up时间也能缩短不少。
1.2 WRF-Chem与CAMS之间的三座大山:坐标系、网格、物种定义
CAMS数据存放在全球经纬度网格上,这跟WRF-Chem的兰伯特正形或其他地图投影完全不是一回事。WRF-Chem需要的是在模拟域内均匀分布的格点场,而全球数据直接投影过来会导致插值严重变形。第二座大山是垂直坐标,CAMS用混合sigma-p坐标,WRF-Chem也用eta坐标,但这两种坐标的层顶、层厚分布完全不同,必须做逐层的高度插值。第三座大山是物种定义差异。CAMS里的变量名和WRF-Chem机制里要求的变量名不是一个体系,比如CAMS里叫“有机质aerosol organic matter”,到WRF-Chem的CBMZ机制里可能得按OC或有机气溶胶的某种拆分方式去映射。
这三座大山决定了不能简单用一个通用的“格式转换”工具解决,需要一个专门针对CAMS变量表、坐标系和垂直层设计好的程序,也就是CAMStoWRF存在的理由。
1.3 CAMStoWRF在模拟流程中的定位与工作流程
在实际的WRF-Chem模拟流程里,CAMStoWRF的角色是承上启下。上游是数据下载,下游是WRF-Chem主模拟。它的输入是CAMS数据文件加WRF-Chem自身的wrfinput文件,输出是更新后的wrfinput(含化学初始场)和修正后的wrfbdy(含化学边界场)。这个处理流程非常贴合WRF-Chem原有的运行习惯,所以不动主程序,只改IO文件,风险小,也方便后续反复迭代做敏感性实验。
我自己的使用习惯是先用WPS生成met_em文件并跑通real.exe生成wrfinput,再用CAMStoWRF对wrfinput做化学变量的补充写入。这样气象场部分还是由WPS和real保证一致性,CAMStoWRF只专注化学场。整个流程做顺了以后,可以像流水线一样跑批量个例,大大节省人工配置时间。
2. CAMS数据下载实操:从CDS注册到批量脚本落地
这部分看起来最简单,实际上很多人最后都卡在下载这步。CAMS数据存放在CDS平台上,有网页交互式下载和API下载两种方式,跑模拟当然推荐后者,因为要下载的数据文件多,一个时段动辄几十个变量,手动点网页能点到怀疑人生。
2.1 注册CDS账号与API认证
去CDS平台做账号注册是第一步。注册过程不复杂,用常用邮箱注册就行。重要的是注册完成后,在个人页面能看到自己的用户ID(UID)和API Key,这两个信息是API下载的凭证。
下载前需要在本机配置认证文件。拿Linux环境举例,在用户根目录下创建一个名为 .cdsapirc 的文件,内容格式如下:
url: https://cds.climate.copernicus.eu/api key: UID:你的API密钥注意URL和key这两行之间不要有多余字符,文件权限建议设置成600,避免密钥泄露。配置好之后,安装CDS专用下载包就可以开始用脚本取数了。这里有个容易踩的坑:早期版本的CDS API包和新版数据接口有差异,安装时要确认下载的包版本能匹配目标平台。否则会出现请求发出去,返回404或者提示“request not found”之类的报错。
2.2 数据下载脚本写法与参数选择
CAMS数据下载的核心是构造一个request字典,里面写入数据产品名、变量、年份月份、层级类型、区域范围、时间分辨率等信息。我常用的一个最小下载脚本长这样:
import cdsapi server = cdsapi.Client() server.retrieve( 'cams-global-atmospheric-composition-forecasts', { 'variable': [ 'nitrogen_dioxide', 'sulphur_dioxide', 'ozone', 'carbon_monoxide' ], 'model_time': '00:00', 'date': '2024-01-01/2024-01-02', 'type': 'forecast', 'pressure_level': '1000/975/950/925/900/850/800/700/600/500/400/300/200/150/100/70/50/30/20/10', 'data_format': 'grib', 'area': [50, 100, 20, 130], }, 'cams_chem.grib' )脚本里头这几项参数要特别留心。
variable选择决定了你最终能拿到哪些化学物种,建议在转换前把CAMS提供的变量菜单完整看一遍。有些变量不常用,但恰恰是机制里必须的,提前漏选了,后面转换时就会报“这个变量找不到”的错。pressure_level这行,不同任务的使用差异很大,有的场景走地面到对流层就够,有些走臭氧柱分析想拉到平流层。常规WRF-Chem模拟中,我倾向于下载到10hPa左右,大概覆盖到平流层下部,这对边界层高层的化学物质交换很有帮助。area是经纬度范围,这里强烈建议设置为比WRF模拟域四周外扩至少1到1.5度,防止插值时边界不足出现空白区。
还有一个重要参数是type,CAMS有analysis和forecast之分。分析场贴近实况,但有些时间段只有预报场可用,需要根据个例时间段去适配合法的type设置和时间步长(from 0 to 6 by 3这类设置)。
2.3 数据格式选择:GRIB还是NetCDF
CDS返回的数据格式有grib和netcdf可选。CAMStoWRF的原生读取能力偏向GRIB,GRIB文件能保留完整的变量元数据和层级信息,在某些情况下文件体积也相对可控。但GRIB文件对常见Python读取来说不够友好,需要eccodes这类底层库支撑。NetCDF的好处是生态好,容易用Python可视化检查。
我个人经验是:如果是要直接交给CAMStoWRF处理,优先下GRIB格式,省去后续格式转换步骤。如果下载后先用Python做质量检查、绘画平面图,或者送入其他前处理流程,那NetCDF更方便。不管选哪种,都要保证下载时勾选了“所有需要的化学变量”,不要只拿默认的那几项。因为Once你发现缺变量,重新下载不仅浪费流量,更浪费你重新匹配时间和变量的精力。
2.4 存储规划与分批下载建议
CAMS全球数据文件的体积不小,如果下载精细度很高、区域覆盖大、时间跨度长,一次请求可能返回几十GB数据。考虑模拟一般只覆盖几天到一周,建议按个例分批次下载,一天一天地下,不要一次性拉整个月。这样有两个好处:一是单个下载任务更稳定,不容易出现网络中断导致整包重来;二是方便你按日期做文件归档。
下载落地之后的目录结构可以这样组织:
data/cams/20240101/ data/cams/20240102/ data/cams/20240103/每个目录里放对应日期的GRIB文件,命名尽量带时间和变量段信息,比如cams_20240101_00_chem.grib。这一步看着无关紧要,实际会为我们后面写配置脚本提供极大便利,尤其是做整月批量转换时,目录规范能帮助你只用简单循环就完成全部处理。
3. CAMStoWRF转换实现:编译、配置、运行三步走
数据拿到手之后,紧接着的重头戏就是CAMStoWRF工具本身的落地。别看这个工具名字很直接,从编译到配置,每一步都能让人卡上半天。
3.1 依赖库与编译环境
CAMStoWRF用Fortran写成,依赖关系主要集中在NetCDF库和GRIB解析库上。要编译它,你首先需要确保系统里有可用的NetCDF Fortran接口,能处理NetCDF4格式文件。GRIB读取方面,需要安装grib_api或者eccodes,并确保编译时能够找到对应的include和lib。很多人的经验是,用环境管理器专门建一个编译环境,把所有依赖装在统一路径下,避免与系统自带库版本冲突。
编译操作本身不算复杂,进入工具源码目录后,首先确认Makefile或configure脚本中的库路径设置,然后执行make完成编译。这里有一个特别常见的坑:编译器版本和库的位长必须一致。比如用Intel编译器编NetCDF库,CAMStoWRF也用同样一套Intel环境去编,不要混合使用GCC和Intel的库文件,否则会在链接阶段报出一堆“undefined reference”。
实际编译过程中,还可能需要调整宏定义,以适配本地NetCDF库的版本。老版本NetCDF会用nf_create等接口,新版本差不多也兼容,但Fortran编译器的flag可能不同,比如需要将默认优化级别调低,否则某些机器上会出现浮点异常。如果在一台比较老旧的Linux服务器上编译失败,优先检查是不是优化flag的问题。
3.2 文件命名、namelist配置与变量映射
编译成功后,使用CAMStoWRF的关键在于配置输入输出文件和变量映射。工具运行需要读取一个namelist文件,里面指明输入CAMS文件路径、输出文件名(通常是wrfinput_d01和wrfbdy_d01)、模拟起止时间、网格嵌套层数等。
文件命名方面,CAMStoWRF要求CAMS数据文件遵循特定命名规范,一般是前缀加上日期时间,比如cams_2024010100. 命名不标准的话,工具在内部调表时就会找不到对应文件。另一件容易忽略的事是:wrfinput_d01文件必须是执行过real.exe之后得到的标准文件,不能拿一个空壳子或自编数组去尝试。
变量映射部分,通常在源码目录内有一个变量对应关系表,列出CAMS原变量和WRF-Chem目标变量的对应关系。比如CAMS的“ozone”对应WRF-Chem的“o3”,“carbon_monoxide”对应“co”。气溶胶部分的映射要小心处理,CAMS提供“organic_matter_aerosol”“black_carbon_aerosol”“sea_salt_aerosol”“dust_aerosol”等类别,而WRF-Chem里可能区分为亲水性和疏水性组分,并有多个粒径bin段,这时需要在映射表中做拆分规则配置。默认映射表不一定适合你指定的化学机制,换了机制以后,一定要手动检查映射关系,把没映射的变量补上,把映射错乱的变量改正。
3.3 运行输出与wrfinput/wrfbdy一致性核查
配置好之后,运行CAMStoWRF通常会输出一行行日志,告诉你正在处理哪个时间层、读取了哪些变量、插值完成、写入了哪些字段。肉眼看到好多个“successfully”并不代表万事大吉,还得自己去核查输出结果是否合理。
我的标准核查动作有三步。第一步是查看wrfinput_d01里的化学变量是否已经更新,可以借助ncdump检查变量维度是否跟气象场一致。第二步是用Python画一张模拟域内的O3或PM2.5空间分布图,与CAMS原始数据的分布趋势做肉眼对比。若出现大片零值或异常极大值,多半是插值范围设置错误或者映射没有完全生效。第三步是打开wrfbdy文件,检查边界网格点上的化学变量是否按照设定频率正确更新,没有出现时间维全零的情况。
比较常见的现象是:wrfinput更新了,但wrfbdy没写好。这通常是因为边界文件的变量名格式与工具默认不匹配,或者namelist里没有把“write_bdy”逻辑打开。我自己的经验是,在运行之前先备份原始的wrfinput和wrfbdy,万一跑出问题,还可以对比原始文件和输出文件之间的差异,快速定位问题出现在化学场写入阶段还是边界更新阶段。
4. 高频问题与避坑清单
这工具本身不算复杂,但和真实模拟项目结合起来后,各种各样的坑就冒出来了。下面列几个我遇到过的典型问题,以及对应的排查思路,方便直接“抄作业”。
| 问题现象 | 可能原因 | 排查思路 |
|---|---|---|
| wrfinput化学变量全为0 | CAMS文件变量没选全 | 在映射表里逐项核对变量是否存在 |
| wrfbdy边界化学场没有更新 | namelist边界写入开关未配置 | 查看输出日志,检查bdy写入函数是否调用 |
| 模拟初始时刻O3浓度异常高 | 压层选择偏高,或CAMS时间与分析时刻不匹配 | 比对CAMS变量在目标时刻的平面分布 |
| GRIB读取报错,无法识别文件 | 缺少eccodes或grib_api特定版本 | 用grib_ls命令检查GRIB文件是否读取正常 |
| 编译阶段intel库和netcdf库混杂 | 编译环境不一致 | 统一用同一套编译器环境重新编译 |
4.1 时间错位与AOD对比表现异常
有一次我跑某次污染过程模拟,模式前6小时的PM2.5空间分布总跟实况差着几个经纬度的偏移,AOD对比也一直偏高。一开始以为是化学机制参数的问题,排查了半天,最后才意识到CAMS数据文件时间名比模拟时间早了一个时次,相当于初始场用了三个小时前的化学态。时间错位这种问题极其隐蔽,因为不报错、不变态,只是结果不对。
建议在下载落地之后,用一个简单脚本批量打印每个GRIB文件里的时间变量,和模拟起止时间做严格比对,一个数据一个数据地核对,避免“想当然”的对应。涉及跨日、跨月的个例时尤其要小心,有些下载请求过了UTC时间边界,回来的文件可能多一段或缺一段。
4.2 气溶胶物种映射对不齐
气溶胶映射是CAMStoWRF用起来最头疼的地方。CAMS气溶胶类别中,海盐和沙尘都有分粒径段的说法,而WRF-Chem机制内的气溶胶模块(比如GOCART或者MOSAIC)对种类、粒径数、混合状态的要求都不一样。如果默认映射没有把CAMS的海盐质量拆到WRF-Chem对应的细/粗模态上,模拟出来的沿海区域气溶胶浓度就普遍偏低,出现奇怪的经纬向梯度。
我的处理方式是先明确自己用的是哪一种WRF-Chem气溶胶方案,再回头改映射表。比如GOCART方案下,海盐通常分多个bin,CAMS的sea_salt_aerosol需要按照一定的质量比例分配到对应bin里。这个比例没有普适答案,只能根据模拟区域和个例去测试调整。不要指望默认映射一步到位,多跑两次敏感性实验,比较模拟和观测的差别来判断拆分是否合理。
4.3 GRIB读取与编译环境的幺蛾子
CAMStoWRF编译过程中的障碍,有一半以上是由GRIB读取库引起的。常见报错是“GRIB_API version too old”或者“eccodes not found”。遇到这种问题,不要急着改代码,先用系统命令检查库版本和安装路径。
考虑到大多数人用的是Intel编译器,建议使用Linux发行版自带的包管理统一安装依赖。如果自己手动编译eccodes,记得在configure阶段打开Fortran接口支持,否则编译CAMStoWRF时完全找不到函数入口。另外,某些系统上存在多版本grib库并存的问题,环境变量LD_LIBRARY_PATH会决定程序运行时到底找哪套库,报错很随机。解决办法是在运行之前手动export一套明确的库路径,并echo确认当前生效的是自己需要的哪个版本。
4.4 嵌套域边界和更新频率的取舍
多嵌套域模拟是CAMStoWRF使用中最需要提前规划的变量。如果你跑的是三层嵌套,每一层都需要对应的wrfinput_d01、wrfinpurt_d02等文件,并且各自生成的wrfbdy要配套对应。有人只处理了外层域,内层域直接沿用默认边界,模拟结果自然出现很明显的边缘污染高值。
化学边界场的更新频率也值得思考。WRF-Chem气象边界一般可以用3或6小时间隔,化学边界场的稳定性取决于你模拟区域的大小。区域大、物种寿命长、化学梯度平缓时,6小时间隔也够用;区域小、靠近强污染源时,3小时更新更好。CAMStoWRF多数版本能按时间循环输出多层边界场,配置时确认每一层的输出时间序列都覆盖完整模拟时段。
这块我在实际赶工阶段吃过亏。最初只把d01边界场备好了,d02和d03直接用父域插值,结果污染气团从边界灌进来以后在子域内完全变形,严重干扰了源解析结论。后来重新把所有嵌套域的wrfinput、wrfbdy全部跑通,整个模拟才稳定下来。嵌套域的处理建议提前写进批处理脚本里,一次性逐域生成,省时省力还安全。
最后一点个人体会
跑过几次CAMStoWRF的完整流程,我的真实感受是:工具本身不复杂,但环境依赖、变量映射和数据时间线这三个地方最容易出岔子。后来我把所有下载脚本、映射表、namelist以及核查程序都固化到一个项目模板目录里,每次接新个例只需要改日期和区域,流程稳定很多。这套流程也让我切身体会到,数值模拟前处理的质量直接决定了模拟结果的可靠性,花在初边界场检查上的时间,永远比后续反复调参数更值得。
最近一次做某区域夏季臭氧个例模拟,我特意把CAMS初始场跑完后,先画了一张O3垂直剖面图,再和探空观测对比,确认高层浓度形态对上了才放心交给WRF-Chem跑后面的预报。这种“跑前检查”的习惯,虽然多花了半小时,却能在后面几天的模拟周期里省出数倍的试错成本。如果你也正在折腾WRF-Chem的化学初边界场,建议把这套流程完整走一遍,把每一步的数据都留档,再回头你就会发现,化学初边界场不再是玄学。