首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >数值模式 | WRF 批量实验脚本怎么写

数值模式 | WRF 批量实验脚本怎么写

作者头像
用户11172986
发布2026-05-26 20:43:03
发布2026-05-26 20:43:03
1700
举报
文章被收录于专栏:气python风雨气python风雨

数值模式 | WRF 批量实验脚本怎么写

上篇聊完对流方案怎么选,师弟把文献筛完了,候选池里列了几套方案。然后他跑来问我第二个问题:师兄,我是在超算上手动创建四个文件夹,一个一个改 namelist 再一个一个提交吗?

我说你可以先跑完一个,剩下的看看我发你的脚本。

上篇说过,批量试验的核心原则是「控制变量」——所有组除了 cu_physics 之外,其他参数必须完全一致。你手动改四遍 namelist,稍微手滑多删了一个逗号,或者复制漏了一行 cudt,四组结果之间的差异就不再单纯是方案差异,而是混合了你操作失误的噪音。这种事后评估的时候根本发现不了,你只会觉得某个方案效果差,但其实是你 namelist 写错了。

所以这一篇讲讲怎么用脚本批量生成实验目录,一键改 namelist,让你的对照组真正「干净」。

脚本要解决什么问题

批量试验的机械性操作无非三件事:克隆文件、修改参数、提交作业。动手前先把这个流程想清楚:

image
image

image

这个流程说起来简单,但手动作业的时候最容易出错的地方恰恰在 B→D 这两步。脚本的价值就是把这两步变成一行命令,消除人为差异。

脚本分块拆解

下面这个脚本我写了比较详细的注释,你可以直接复制到超算上改几个变量就能用。我不贴完整的一坨代码,分块来讲。

第一块:用户配置区

这是你唯一需要改的地方。路径换成你自己的,实验方案按你的候选池填。

代码语言:javascript
复制
# 基础 namelist 路径(以此为模板,sed 替换参数)
BASE_NAMELIST="/home/yourname/wrf_project/ref_run/namelist.input"

# 参考目录(已包含 wrf.exe、met_em、wrfbdy、wrfinput 等所有必需文件)
SOURCE_DIR="/home/yourname/wrf_project/ref_run"

# 所有实验放在一个根目录下
EXP_BASE="/home/yourname/wrf_project/experiments"

# 文件复制方式:link 省空间但要求不删源文件,copy 稳妥但占磁盘
COPY_MODE="link"

这里 COPY_MODE 值得多说两句。WRF 的 met_em 文件和 wrfinput 动辄几十 GB,如果你的候选方案有五六组,copy 模式会直接把磁盘打满。link 模式创建软链接,只占 inode 不占数据块,但有个隐患——源目录被误删了所有实验目录都会断链。我的习惯是:参考目录放在一个专门的路径下,只读不写,然后所有实验目录 link 过去。这样既省空间,又不担心误操作。

第二块:实验方案定义

这一块是核心。每一行定义一个实验组,格式是 实验名|mp_physics|cu_physics|bl_pbl_physics|sf_sfclay_physics|备注

代码语言:javascript
复制
EXPERIMENTS=(
    "OLD|9,   8,   8|1,   0,   0|2,   2,   2|2,   2,   2|"
    "EXP_A|8,   8,   8|1,   0,   0|2,   2,   2|2,  2,  2|"
    "EXP_B|9,   8,   8|6,   0,   0|2,   2,   2|2,  2,  2|"
    "EXP_C|8,   8,   8|14,  0,   0|2,   2,   2|2,  2,  2|"
    "EXP_D|8,   8,   8|1,   0,   0|2,   2,   2|2,  2,  2|"
)

这里有几个设计细节你可能注意到:

为什么 cu_physics 的 d02 和 d03 都是 0? 因为 d02 设的 9 km,d03 设的 3 km,按上篇讲的灰区原则,3 km 内层直接关对流参数化(cu_physics = 0)。如果你的嵌套方案不同,自己改数字就好。

为什么每组实验名前面都保留了一个 OLD 作为基线? 这个习惯很重要。批量对比里必须有一组参考基线,一般是你们课题组之前一直在用的默认配置,或者文献里最常见的组合。没有基线,你跑完四组不同的结果,说不出谁好——你只能说 A 最优,但不知道 A 比默认配置提升了多少。

备注列写清楚改动逻辑。两个月后你回头看你跑过的实验,大概率已经忘了 EXP_C 改了哪个参数、为什么这么改。备注就是给你未来的自己看的。

第三块:文件克隆逻辑

代码语言:javascript
复制
clone_item() {
    local src="$1"
    local dst="$2"
    local mode="$3"
    local basename=$(basename "$src")

    # 跳过 wrfout 和日志文件——这些在参考目录里可能是残留物
    case"$basename"in
        wrfout*|rsl.out*|rsl.error*|slurm-*.out|*.log|namelist.input|run.log)
            return 0
            ;;
    esac

    if [[ "$mode" == "copy" ]]; then
        [[ -d "$src" ]] && cp -rL "$src""$dst" || cp -L "$src""$dst"
    else
        ln -sf "$src""$dst"
    fi
}

这里的坑有两个。

第一个是 wrfout* 这类输出文件。如果你的参考目录之前跑过一轮,里面可能残留了上一次的 wrfout,几十上百 GB。如果不跳过它们,link 或 copy 过去之后每个实验目录都会多出一堆废数据,然后你在超算上可能收到管理员的「磁盘超额」邮件。

第二个是 namelist.input。参考目录里会有一个模板 namelist,但我们要写入每组的定制版本,所以克隆时跳过它,克隆完再单独写。

第四块:sed 替换与写入

代码语言:javascript
复制
sed \
    -e "s|^[[:space:]]*mp_physics[[:space:]]*=.*,[[:space:]]*$| mp_physics                          = ${MP_PHYSICS},|" \
    -e "s|^[[:space:]]*cu_physics[[:space:]]*=.*,[[:space:]]*$| cu_physics                          = ${CU_PHYSICS},|" \
    -e "s|^[[:space:]]*bl_pbl_physics[[:space:]]*=.*,[[:space:]]*$| bl_pbl_physics                      = ${PBL_PHYSICS},|" \
    -e "s|^[[:space:]]*sf_sfclay_physics[[:space:]]*=.*,[[:space:]]*$| sf_sfclay_physics                   = ${SFC_PHYSICS},|" \
    "$BASE_NAMELIST" > "$OUT_NAMELIST"

很多人看到这一串 sed 就头大,我说一下为什么用 sed 而不是重新生成整个 namelist。

namelist.input 不是只有物理参数这一节。&time_control&domains&dynamics 里可能有几十行,全是跟你的个例、区域、起报时间绑定的,这些在批量对比里必须一模一样。如果重新生成整个文件,你等于要维护两份 namelist(一份完整模板加一份参数对照表),改一次起报时间就要同步两处——迟早会忘。

sed 的方案是「基于一个已验证的完整 namelist,只替换需要变化的四行」,其余原封不动保留。这是控制变量的最小侵入方式。

但这并不意味着 sed 的正则表达式可以闭着眼睛写。 上面那四个正则里 ^[[:space:]]* 匹配行首可能的空格,=.*, 匹配等号后面任意内容直到行末逗号。如果你的 namelist 里参数后面没有逗号(比如 WRF 4.5 某些配置下不强制要求逗号结尾),这条正则就匹配不上——sed 不会报错,只会静默跳过,最后你跑的四组实验用的还是同一个 cu_physics。

所以脚本末尾我加了一个校验步骤:

代码语言:javascript
复制
grep -E "mp_physics|cu_physics|bl_pbl_physics|sf_sfclay_physics" "$OUT_NAMELIST" | sed 's/^/    /'

它在每次写入后打印替换结果。不要嫌烦,每次跑之前扫一眼输出,确认四组数字确实不一样。

完整脚本

下面是完整脚本。保存为 gen_experiments.sh,在超算上 bash gen_experiments.sh 即可。

代码语言:javascript
复制
#!/bin/bash
# =============================================================================
# WRF namelist 物理方案批量修改脚本(含自动创建实验目录)
# 用途: 基于同一个参考目录,自动克隆出多个实验目录并修改 namelist
# 前提: 参考目录内含完整的 wrf.exe、met_em、表格文件、wrfbdy、wrfinput 等
# =============================================================================
set -euo pipefail

# ============================================================================
# 0. 用户配置区(按需修改)
# ============================================================================

# --- 基础 namelist 路径 ---
# 文件中必须已存在 mp_physics / cu_physics / bl_pbl_physics / sf_sfclay_physics 行
BASE_NAMELIST="/home/yourname/wrf_project/ref_run/namelist.input"

# --- 参考目录(已配置完整的 WRF 运行环境)---
SOURCE_DIR="/home/yourname/wrf_project/ref_run"

# --- 实验根目录 ---
EXP_BASE="/home/yourname/wrf_project/experiments"

# --- 文件复制模式: "link" 创建软链接(省空间), "copy" 硬拷贝 ---
COPY_MODE="link"

# --- 实验方案定义: 实验名|mp|cu|pbl|sfc|注释 ---
# 每组三个域 (d01, d02, d03),值之间用逗号分隔
EXPERIMENTS=(
    "OLD|9,   8,   8|1,   0,   0|2,   2,   2|2,   2,   2|"
    "EXP_A|8,   8,   8|1,   0,   0|2,   2,   2|2,  2,  2|"
    "EXP_B|9,   8,   8|6,   0,   0|2,   2,   2|2,  2,  2|"
    "EXP_C|8,   8,   8|14,  0,   0|2,   2,   2|2,  2,  2|"
    "EXP_D|8,   8,   8|1,   0,   0|2,   2,   2|2,  2,  2|"
)

# ============================================================================
# 1. 辅助函数
# ============================================================================

log_info()  { echo -e "\033[32m[INFO]\033[0m  $*"; }
log_warn()  { echo -e "\033[33m[WARN]\033[0m  $*"; }
log_error() { echo -e "\033[31m[ERROR]\033[0m $*"; }

# 复制或链接文件/目录
clone_item() {
    local src="$1"
    local dst="$2"
    local mode="$3"
    local basename=$(basename "$src")

    # 跳过输出类文件和日志
    case"$basename"in
        wrfout*|rsl.out*|rsl.error*|slurm-*.out|slurm-*.err|*.log|namelist.input|run.log)
            return 0
            ;;
    esac

    if [[ "$mode" == "copy" ]]; then
        if [[ -d "$src" ]]; then
            cp -rL "$src""$dst" 2>/dev/null || true
        else
            cp -L "$src""$dst" 2>/dev/null || true
        fi
    else
        ln -sf "$src""$dst" 2>/dev/null || true
    fi
}

# ============================================================================
# 2. 主程序
# ============================================================================

echo"============================================"
echo"WRF namelist 物理方案批量修改"
echo"参考目录: ${SOURCE_DIR}"
echo"实验根目录: ${EXP_BASE}"
echo"复制模式: ${COPY_MODE}"
echo"============================================"

# 基础校验
if [[ ! -f "$BASE_NAMELIST" ]]; then
    log_error "基础 namelist 不存在: $BASE_NAMELIST"
    exit 1
fi

if [[ ! -d "$SOURCE_DIR" ]]; then
    log_error "参考目录不存在: $SOURCE_DIR"
    exit 1
fi

mkdir -p "$EXP_BASE"

for exp_def in"${EXPERIMENTS[@]}"; do
    IFS='|'read -r EXP_NAME MP_PHYSICS CU_PHYSICS PBL_PHYSICS SFC_PHYSICS DESC <<< "$exp_def"

    EXP_DIR="${EXP_BASE}/${EXP_NAME}"
    OUT_NAMELIST="${EXP_DIR}/namelist.input"

    echo""
    echo"--- 实验: ${EXP_NAME} ---"
    echo"  描述: ${DESC}"

    # --- 2.1 创建实验目录并克隆文件 ---
    if [[ ! -d "$EXP_DIR" ]]; then
        log_info "创建实验目录: ${EXP_DIR}"
        mkdir -p "$EXP_DIR"

        log_info "从参考目录克隆文件 (mode=${COPY_MODE})..."
        for item in"$SOURCE_DIR"/*; do
            [[ -e "$item" ]] || continue
            clone_item "$item""$EXP_DIR/""$COPY_MODE"
        done

        # 删除参考目录带过来的 namelist(后面会写入定制版)
        rm -f "$EXP_DIR/namelist.input"

        log_info "目录初始化完成"
    else
        log_warn "目录已存在,跳过文件克隆,仅更新 namelist.input"
    fi

    # --- 2.2 写入 namelist ---
    sed \
        -e "s|^[[:space:]]*mp_physics[[:space:]]*=.*,[[:space:]]*$| mp_physics                          = ${MP_PHYSICS},|" \
        -e "s|^[[:space:]]*cu_physics[[:space:]]*=.*,[[:space:]]*$| cu_physics                          = ${CU_PHYSICS},|" \
        -e "s|^[[:space:]]*bl_pbl_physics[[:space:]]*=.*,[[:space:]]*$| bl_pbl_physics                      = ${PBL_PHYSICS},|" \
        -e "s|^[[:space:]]*sf_sfclay_physics[[:space:]]*=.*,[[:space:]]*$| sf_sfclay_physics                   = ${SFC_PHYSICS},|" \
        "$BASE_NAMELIST" > "$OUT_NAMELIST"

    log_info "已写入 namelist: ${OUT_NAMELIST}"

    # 校验替换结果
    grep -E "mp_physics|cu_physics|bl_pbl_physics|sf_sfclay_physics""$OUT_NAMELIST" | sed 's/^/    /'

done

echo""
echo"============================================"
echo"全部完成。请进入各实验目录手动提交 Slurm 作业。"
echo""
echo"示例:"
echo"  cd ${EXP_BASE}/EXP_A && sbatch run_wrf.slurm"
echo"============================================"

跑完之后

脚本执行完,你的 experiments/ 目录下会有这些文件夹:

代码语言:javascript
复制
experiments/
├── OLD/            
├── EXP_A/        
├── EXP_B/        
├── EXP_C/        
└── EXP_D/       

每个文件夹里都有完整的 WRF 运行环境,且 namelist 中的四行关键参数已经替换为对应方案的值。接下来逐一手动 sbatch 提交,或者自己再加一个循环自动提交。

代码语言:javascript
复制
# 手动逐个提交(推荐,可以在提交前再检查一下 namelist)
for exp in OLD NEW_BAD EXP_A EXP_B EXP_C EXP_D; do
    echo "提交 ${exp}..."
    cd "${EXP_BASE}/${exp}" && sbatch run_wrf.slurm
done

两个容易被忽略的坑

1. namelist 里参数结尾有没有逗号,sed 正则的行为完全不同。 上面脚本假设所有参数行以逗号结尾(WRF 标准写法)。如果你的 namelist 里某一行没有逗号,比如 mp_physics = 6, 8, 8 写成了 mp_physics = 6 8 8(没逗号但 WRF 其实能读),那正则 =.*, 匹配不到,sed 跳过不报错。你跑半天发现所有组 mp_physics 都一样——检查方法就是看脚本末尾的 grep 校验输出是不是四组不同的数字。

2. link 模式下修改参考目录文件会影响所有实验。 假设你用 COPY_MODE="link",然后某天手滑在 ref_run/ 下改了一个 wrf.exe 重新编译了一版。所有实验目录因为软链接指向同一个文件,会全部用上新版 executable。


师弟按我给的脚本跑了一轮。两天之后他发消息说,师兄,不好,超算账号余额蒸发了

完啦,good luck,快呼叫导师。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-05-23,如有侵权请联系 cloudcommunity@tencent.com 删除
目录
  • 数值模式 | WRF 批量实验脚本怎么写
    • 脚本要解决什么问题
    • 脚本分块拆解
      • 第一块:用户配置区
      • 第二块:实验方案定义
      • 第三块:文件克隆逻辑
      • 第四块:sed 替换与写入
    • 完整脚本
    • 跑完之后
    • 两个容易被忽略的坑
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档