更新ecFlow系统支持分钟级产品
本文介绍如何对 MESO 后处理子系统进行改造以生成分钟级数据和图片产品。
声明:本文仅代表笔者个人观点,文章中引用的相关材料仅用于探讨和交流,不能作为最终事实依据。 关于模式系统的相关信息,请以官方发布的信息及经过同行评议的论文为准。如有任何疑问,建议查阅权威资料或咨询相关领域的专家。
概述
CEMC 运行的 MESO 系统模式间隔 1 小时输出,生成逐 1 小时产品。 下图展示了 MESO 系统生成的一幅图片产品。 图片的左下角和右下角标注了产品对应的起报时次 (2025031200)、预报时效 (24h) 和预报时刻 (2025031300),预报时效和预报时刻都精确到小时。
图 MESO 图片产品示例,可以通过如下网址访问 NMC 网站
CEMC 也同时协助多个区域中心部署小区域 MESO 系统。 其中一个区域系统要求提供前 3 小时逐 10 分钟的输出。
因 CEMC 的 MESO 系统尚未生成分钟级产品,本文记录笔者目前对区域部署的 MESO 后处理子系统的改造尝试,使其支持分钟级模式输出数据,生成分钟级数据和图片产品。
注:本文介绍方案是在较短任务期限内形成的临时解决方案,仅针对当前任务需求进行改造,未来会进一步开发更具有通用性的解决方案。
本文首先介绍现有 MESO 后处理子系统的关键技术细节,然后介绍支持分钟级产品的改造方案,最后介绍具体实现。
现状
MESO 系统由三个独立的子系统构成:
- 观测资料检索子系统:检索并预处理模式需要的观测资料,在模式预报子系统启动前运行
- 模式预报子系统:主要运行资料同化和模式积分任务
- 后处理子系统:主要运行数据产品和图片产品生成任务,与模式预报子系统同时运行
模式预报子系统的模式积分任务运行模式积分程序生成一段时间内的预报结果,比如 MESO 系统 00/12 时次预报未来三天的天气。 模式程序运行过程中每积分一定时长会输出表示该时刻预报结果的二进制格式文件,MESO 系统每积分一个小时会输出一个模式面 modelvar 文件和一个等压面 postvar 文件。
后处理子系统的数据检查任务监控模式预报子系统模式输出的 modelvar 文件,每当一个时效 modelvar 文件生成,则会触发对应时效的产品制作任务。
下图展示了 MESO 后处理子系统的运行流程,由数据产品和图片产品两个子流程构成。
图 MESO 后处理子系统运行流程,包括数据产品 (post) 和图片产品 (graph) 两部分。两部分在同一个 ecFlow 流程中运行,也可以在不同 ecFlow 流程中运行
具体流程如下:
- 数据产品子流程的数据检查任务 (post initial) 检查模式预报子系统输出的 modelvar 文件。 每检查到某个时效文件生成,则会发布对应时效的事件 (例如 event 000),触发后续的数据产品制作任务。
- 后处理任务 (data2grib2) 对二进制的模式原始输出文件进行后处理,输出 GRIB2 格式的数据产品,并由基础 GRIB2 数据生成任务 (grib2-orig) 生成基础 GRIB2 数据产品。 随后,使用基础产品生成派生数据产品。
- 图片产品子流程的数据检查任务 (graph initial) 检查数据产品子流程生成的基础 GRIB2 产品。 每检查到某个时效文件生成 (例如 event 000),则会发布对应时效的事件,触发后续的图片产品制作任务。
- 图片产品制作任务 (plot) 生成图片产品并完成产品上传 (upload)。注意,部分图片产品依赖多个时效的数据 (例如降水图形需要两个文件作差),甚至使用所有时效的数据 (例如时序图)
上述流程针对小时级产品设计,比如数据检查程序的事件使用 3 位数字表示小时数 (000, 001, …, 072)。 因为 MESO 每个预报时刻输出一个包含全部要素的文件,对于数据产品和图片产品来说,数据对应的预报时刻是小时级还是分钟级并不影响该产品的制作。 比如使用 NCL 脚本绘图时,NCL 脚本从数据文件中加载需要的要素场,并从参数文件或者命令行参数获取图片对应的时间信息,而不是自动从文件中识别。 因此,对后处理子系统改造的关键在于如何让各个任务正确地找到输入数据文件。
下面介绍后处理子系统的改造方案。
方案
下图展示了改造后的 MESO 后处理子系统运行流程。
图 改造后的 MESO 后处理子系统运行流程,支持前 3 小时逐 10 分钟输出数据
因模式积分程序配置相关原因,为了在较短任务期限内实现,模式预报子系统运行两个模式积分任务:
- fcst_3h:执行前 3 小时积分,间隔 10 分钟输出
- fcst:执行完整积分,间隔 1 小时输出
两个积分的初值条件完全一样,因此前 3 小时整点输出的 3 个文件 (000, 001, 003) 也完全一致。 对于后处理任务来说,无论文件保存在哪里,只要能找到该文件即可。 MESO 后处理子系统中使用数据查找工具 nwpc-data-client 实现这一目标,通过定制的配置文件在多个目录中同时查找所需的数据文件。
小时级输出的后处理子系统中使用 3 位整数区分不同时效,并为每个时效的任务设置 ECFLOW 变量FTHOUR
。
为了保持系统的延续性,在分钟级输出的后处理子系统中保留 3 位数字的小时时效变量 FTHOUR
,并增加 2 位数字的分钟时效变量 FTMNIUTE
。
同时,为了更直观表示时效,在 ecFlow 的任务名和 initial 任务的事件名中均使用 FFFhMMm
格式表示时效,其中 FFF
表示小时,MM
表示分钟。
在 ecFlow 流程定义脚本、ecFlow 任务运行脚本和 NCL 绘图脚本中均需要增加对分钟级产品的支持,下面介绍部分关键模块的具体实现。
实现
数据查找工具
数据产品子流程的 initial 任务使用 nwpc-data-client 工具的 nwpc_data_checker
命令批量检查目录中的多个文件是否存在。
调用命令如下所示:
echo ${forecast_list} |
nwpc_data_checker local \
--data-config-dir=${data_client_config_dir} \
--location-level=${data_client_location_level} \
--data-type=${data_type} \
--max-check-count=${max_check_count} \
--check-interval="${sleep_seconds_for_check}s" \
--delay-time "${sleep_seconds_for_next_time}s" \
--execute-command "$(which ecflow_client) --event grapes_modelvar_{.ForecastTime | getForecastHour | printf \"%03d\"}h{.ForecastTime | getForecastMinute | printf \"%02d\"}m" \
${START_TIME}
其中 ${forecast_list}
是时效字符串列表,要体现出前 3 小时逐十分钟、3 小时后逐小时的数据情况,例如 000h00m 000h10m
等。
下面代码使用 seq
和 awk
命令生成时效字符串:
minute_level_forecast_hour_length=3
forecast_list=""
for h in $(seq 0 2); do
for m in $(seq 0 10 60); do
forecast_list="${forecast_list} ${h}h${m}m"
done
done
forecast_list="${forecast_list} $(seq ${minute_level_forecast_hour_length} ${FORECAST_LENGTH} | awk '{print $1 "h"}')"
数据文件名的规则体现在 nwpc-data-client 的配置文件中。
下面代码节选自 cma_meso_1km_v6.0/bin/modelvar.cold.yaml
配置文件。
可以看到文件名中包含时效的 3 位小时和 2 位分钟,目录列表中同时列出了 run_3h 和 run 两个目录。
file_names:
- modelvar{.Year}{.Month}{.Day}{.Hour}{.ForecastTime | getForecastHour | printf "%03d"}{.ForecastTime | getForecastMinute | printf "%02d"}
paths:
- type: local
level: runtime
path: /share/home/meso_app/OPER/WORKDIR/cma_meso_1km/cold/{.Hour}/fcst/grapes_model/run_3h
- type: local
level: runtime
path: /share/home/meso_app/OPER/WORKDIR/cma_meso_1km/cold/{.Hour}/fcst/grapes_model/run
图片产品子流程的 initial 任务和上述类似。
下面代码节选自 cma_meso_1km_v6.0/grib2/orig.cold.yaml
配置文件。
因为后处理子系统使用了同样的逻辑生成小时级和分钟级产品,所有 GRIB2 数据产品都保存在同一个目录中,所以这里只列出了一个目录。
file_names:
- rmf.hgra.{.Year}{.Month}{.Day}{.Hour}{.ForecastTime | getForecastHour | printf "%03d"}{.ForecastTime | getForecastMinute | printf "%02d"}.grb2
paths:
- type: local
level: runtime
path: /share/home/meso_app/OPER/WORKDIR/cma_meso_1km_v6_0_post/{.Year}{.Month}{.Day}{.Hour}/data/output/grib2-orig
注:配置文件中的
{.ForecastTime | getForecastMinute | printf "%02d"}
等类似代码属于 GOLANG 中的模板语法,和本文主题关系不大,不再详细介绍。 如需了解详细信息,可以访问 cemc-oper/nwpc-data-client 项目源代码。
ecFlow流程
构建 ecFlow 流程的 Python 脚本中使用 forecast_hour_list
保存时效列表。
更新后的 forecast_hour_list
由如下代码定义:
forecast_hour_list = []
minute_level_forecast_hour_length = 3
minute_level_interval = 10
for hour in range(0, minute_level_forecast_hour_length):
for minute in range(0, 60, minute_level_interval):
forecast_hour_list.append(f"{hour:03d}h{minute:02d}m")
for hour in range(minute_level_forecast_hour_length, self.config["forecast_length"]+1):
forecast_hour_list.append(f"{hour:03d}h00m")
self.config['forecast_hour_list'] = forecast_hour_list
在为每个时效创建任务时,定义时效相关变量 FTHOUR
和 FTMINUTE
:
for a_forecast_time in self.config['forecast_hour_list']:
a_forecast_hour = a_forecast_time[0:3]
a_forecast_minute = a_forecast_time[4:6]
with fm_hours.add_family('{time}'.format(time=a_forecast_time)) as fm_forecast_hour:
fm_forecast_hour.add_variable("FTHOUR", a_forecast_hour)
fm_forecast_hour.add_variable("FTMINUTE", a_forecast_minute)
with fm_forecast_hour.add_task(
"pre_data2grib2_{time}".format(time=a_forecast_time)
) as tk_pre_data2grib2:
...
ecFlow任务脚本
ecFlow 任务脚本需要处理新增的分钟时效变量 FTMINUTE
。
FTIMEH3=%FTHOUR%
FTIMEM2=%FTMINUTE%
每个时效的运行目录需要增加分钟时效部分,从 FFF
改为 FFFhMMm
:
run_dir=${WORKDIR}/${FTIMEH3}h${FTIMEM2}m
test -d ${run_dir} || mkdir -p ${run_dir}
cd ${run_dir}
在后处理程序的配置文件中设置分钟对应参数 end_minute
:
注:参数
step
表示小时时效,当前场景中end_year
、end_month
、end_day
、end_hour
、end_second
等参数没有实际用处。
cat > namelist.input <<EOF
#...
&namelist_05
start_year = $Y4,
start_month = $MM,
start_day =$DD,
start_hour =$HH,
start_minute = 00,
start_second = 00,
step = $FTIMEH3,
end_year = 2013,
end_month = 05,
end_day = 01,
end_hour = 12,
end_minute = $FTIMEM2,
end_second = 00 /
#...
EOF
输出的产品文件名中也需要相应设置分钟时效部分。
if [[ -f gmf.gra.${init_time}${FTIMEH3}${FTIMEM2}.grb2 ]]; then
cp gmf.gra.${init_time}${FTIMEH3}${FTIMEM2}.grb2 \
rmf.hgra.${init_time}${FTIMEH3}${FTIMEM2}.grb2
fi
通用绘图任务脚本中需要修改绘图参数文件生成部分代码,在原有基础上增加分钟时效变量 forecast_minute
。
forecast_hour=%FTHOUR%
forecast_minute=%FTMINUTE%
# ...skip...
export NEWDATE=$(smsdate ${local_start_time} +${forecast_hour} )
cat > grapes_meso_date << EOF
${local_start_time}${forecast_hour}${forecast_minute}
${NEWDATE}${forecast_minute}
EOF
NCL脚本
NCL 绘图脚本调用如下图所示。每类图片对应一个 NCL 脚本 (plot_*.ncl),运行时会调用 cn_map_function.ncl 中定义的函数绘制地图,地图文件保存在 cnmap 目录中。
NCL 脚本生成 ps 格式的矢量图,使用 ImageMagick 的 convert
命令转为 PNG 格式图片。
图 NCL 绘图脚本调用示意图
输入数据:
- 数据文件:rmf.hgra.*.grb2,部分图片使用多个数据文件
- 绘图区域配置文件:region.csv,常量配置文件,所有任务使用同一个配置文件
- 时间配置文件:grapes_meso_date,每张图片新建一个配置文件,包含起报时次和预报时效
小时级绘图的时间配置文件结构如下所示,第一行包含起报时次 (yyymmddhh) 和预报小时 (FFF),第二行是预报时刻 (yyymmddhh)。
yyyymmddhhFFF
yyyymmddhh
修改后的分钟级绘图时间配置文件如下所示,第一行包含起报时次 (yyymmddhh) 、预报小时 (FFF) 和预报分钟 (MM),第二行是预报时刻 (yyyymmddMM)。 因为起报时次一定是整点时刻,所以计算预报时刻时无需考虑分钟。
yyyymmddhhFFFMM
yyyymmddhhMM
NCL 脚本需要相应修改参数配置文件加载、文件名、时间文本信息等部分的代码。
下面代码是图片中左下角和右下角标题字符串。
左下角标题格式为 2025031500+001h20m (UTC)
,右下角标题格式为 202503150120 (UTC)
。
bottom_left_string = utc_start_time_str + "+" + forecast_hour_str + "h" + forecast_minute_str + "m:C:" + cst_start_time_str + "+" + forecast_hour_str + "h" + forecast_minute_str + "m"
bottom_right_string = utc_forecast_time_str + "(UTC):C:" + cst_forecast_time_str + "(CST)"
另外还对部分 NCL 脚本代码进行了少量重构,将解析时间参数文件功能放到一个函数中,不再详细介绍。
应用
本节图片展示了改造后 MESO 后处理子系统 ecFlow 的运行流程。
图 MESO 模式预报子系统,注意 fcst_3h 和 fcst 的触发器设置
图 MESO 后处理子系统数据产品子流程数据检查任务 initial
图 MESO 后处理子系统数据产品子流程单个时效任务
图 MESO 后处理子系统图片产品子流程数据检查任务 initial
图 MESO 后处理子系统图片产品子流程单类图片任务
总结
本文首先介绍了小时级后处理子系统的现状,然后介绍分钟级后处理子系统的改造方案和具体实现,最后展示了 ecFlow 系统的部分截图。
当前方案是短时间为解决具体问题而选择的临时解决方案,尚存在一些问题:
- Shell 脚本和 Python 脚本中的时效列表生成相关代码属于硬编码,不够通用
- 数据查找工具中的延迟检查功能 (对应命令行参数
--check-interval
) 仅针对小时时效开发,未考虑小时、分钟混合的情况,当前实现会因延迟过长时间而拖慢检查速度 - NCL 绘图脚本代码缺乏活跃更新维护,建议使用 Python 重新实现
- 需要考虑如何用一套代码同时支持小时和分钟两种类型的产品后处理系统,包括 ecFlow 流程代码、ecFlow 任务脚本代码和绘图脚本代码
笔者将继续研究产品后处理系统的流程设计和后处理算法的代码实现。