3. 批处理概念与需求:为什么需要批处理、常见批处理场景

说实话,我刚接触 GROMACS 那会儿,也干过一件特别傻的事——手动改温度跑 NPT,一次一次地敲命令、改文件、等结果。一个晚上下来,脖子酸了,手也酸了,事情还没跑完。后来带我的老工程师看了我的工作目录,只说了一句:「你这是在给电脑找活干,还是给自己找活干?」

从那以后,我就彻底明白了——批处理不是锦上添花,而是保命神器。

3.1 为什么需要批处理?

道理其实很简单。分子动力学模拟有三大特性,决定了手动操作很难撑得下去:

  • 重复性高 —— 温度扫描、浓度扫描、多副本模拟,参数变了又变,流程却一模一样。
  • 步骤链条长 —— 从结构准备、能量最小化、NVT 平衡、NPT 平衡到生产模拟,光一组就要敲七八个命令。
  • 中间状态脆弱 —— 一个步骤失败,后面的全白跑。手动操作时你很难及时发现。

我个人的经验是:一个 8 温度 × 3 副本 × 2 力场的扫描任务,如果手动跑,至少需要 2 天盯着屏幕,而且大概率会漏掉某一步的中间文件。如果用脚本写一个批处理,5 分钟提交,睡一觉回来数据全齐。

批处理的核心价值,说白了就是三个词:自动化、可复现、可扩展。

3.2 常见批处理场景之一:温度扫描

温度扫描是最常见的需求之一。你想知道某个体系在不同温度下的相变行为、扩散系数或者结构转变点。手动做?300K、310K、320K ... 一个一个跑,最后还要手动整理数据?想想就头疼。

我曾经遇到过这样一个项目:需要研究一种膜蛋白在 298K 到 350K 范围内,每隔 2K 一个点的脂质有序参数变化。区区 27 个温度点。手动写 mdp 文件、改温度参数、提交作业,到了第 10 个点我就把 320K 写成了 310K,结果重跑了一整夜。

批处理的正确做法是:

  1. 用一个温度列表驱动参数生成。
  2. 自动生成每个温度的 mdp 文件(只改 ref_t 和 gen_temp)。
  3. 依次或并行提交。
  4. 完成后批量提取结果。
# 一个简单的温度扫描 bash 脚本片段
for temp in 298 300 310 320 340 350
do
    mkdir -p T_${temp}
    cp npt.mdp T_${temp}/
    sed "s/REFTEMP/${temp}/g; s/GENTEMP/${temp}/g" npt.mdp > T_${temp}/npt_${temp}.mdp
    echo "Running at ${temp} K ..."
    gmx grompp -f T_${temp}/npt_${temp}.mdp -c npt.gro -p topol.top -o T_${temp}/npt.tpr
    gmx mdrun -deffnm T_${temp}/npt -v
done

我建议不要只生成单个 mdp 文件,而是把 mdp 当作模板(保留所有通用参数),用 sed 或 Python 做占位符替换。这样即使后面需要加新温度,改一行数组就行。

3.3 常见批处理场景之二:浓度扫描

浓度扫描常见于溶液体系:盐浓度、配体浓度、高分子接枝密度等等。本质是改拓扑文件中的分子数目,而不是改力场参数。

你想想看,改拓扑文件里的「分子数目」这一行,手动改 20 个不同的浓度……我要说,不出错才怪。

浓度扫描的批处理逻辑稍微不同:

  • 每个浓度对应一个独立的拓扑文件(或用一个变量控制复制次数)。
  • insane.py / packmol 工具可以配合脚本生成不同浓度的初始结构。
  • 后续跑 MD 的流程完全一致。
# 浓度扫描脚本示例(以 NaCl 浓度为例)
for ionic_str in 0.1 0.15 0.2 0.3 0.5
do
    mkdir -p conc_${ionic_str}
    # 通过 GROMACS 的 insert-molecules 或 genion 调整离子数
    echo "Adding ions for ${ionic_str} M ..."
    gmx grompp -f em.mdp -c initial.gro -p topol.top -o conc_${ionic_str}/em.tpr
    gmx mdrun -deffnm conc_${ionic_str}/em
    
    # 接着做 NVT、NPT⋯⋯
done

注意:浓度扫描时,拓扑文件中的离子数量不能靠手动推算,容易搞错电荷平衡。我曾经吃过这个亏,跑完一条轨迹才发现体系总电荷不为零……后面重跑一遍,一周就没了。强烈建议用 gmx genion 配合脚本自动中和。

3.4 常见批处理场景之三:多副本模拟

多副本模拟(multi-copy / replica simulations)包括:

  • 并行回火(Replica Exchange / REMD)
  • 伞形采样(Umbrella Sampling)
  • 含自由能计算的 FEP / TI
  • 还有最简单的「同条件跑多个独立副本以提高统计精度」

举个真实例子:你想计算一个小分子在水中的溶剂化自由能。FEP 方法的每个 λ 窗口都是一个独立的「副本」,而且每个窗口内部还要做平衡、生产。手动管理 20 个 λ 窗口、每个窗口 3 个步骤……想想就头大。

多副本模拟的批处理核心是:

  1. 用索引(副本编号 / λ 值)命名目录或文件。
  2. 参数化的 mdp 模板(每个副本改不同的 foreign_lambda 或 init_lambda_state)。
  3. 批量提交作业(SLURM 作业数组或 GNU Parallel)。
  4. 分析阶段需要将不同副本的中间数据合并。
# 多副本(FEP 窗口)提交脚本片段(SLURM 作业数组)
#SBATCH --array=0-19
# 假设有 20 个 λ 窗口,λ 从 0 到 1,步长 0.05

lambda_idx=${SLURM_ARRAY_TASK_ID}
lambda=$(echo "scale=3; ${lambda_idx} * 0.05" | bc)

mkdir -p lambda_${lambda_idx}
cp template/* lambda_${lambda_idx}/
cd lambda_${lambda_idx}
sed "s/LAMBDA_VALUE/${lambda}/g" fep.mdp > fep_run.mdp
gmx grompp -f fep_run.mdp -c eq.gro -p topol.top -o fep.tpr
gmx mdrun -deffnm fep -v

你可能会问:为什么不直接在命令行里写循环?我个人的经验是:对于超过 10 个副本的任务,作业数组 + 模板替换是最高效的方式。万一某个窗口崩溃了,重跑那个窗口就行,不影响其他副本。

3.5 批处理概念的本质图景

说了这么多,其实批处理可以抽象成一张概念图。我把核心逻辑画出来,你看一眼就明白了:

批处理自动化核心逻辑 输入参数组 温度列表 浓度列表 / 副本索引 模板引擎 mdp / top 模板 占位符替换 (sed/Python) 执行调度 循环 / 作业数组 并行 / 队列 批量工作目录 & 中间文件 每个任务独立目录,命名规范(T_300K / conc_0.1M / lambda_5) 中间状态自动检测:成功标记文件 / 失败重试 结果汇总 & 清理 批量提取能量、轨迹、参数 输入参数 → 模板引擎 → 执行调度 → 工作目录 → 结果汇总

这张图里,我把批处理分解成五个环节:输入参数组(温度、浓度、副本索引) → 模板引擎(生成各任务的配置) → 执行调度(循环或作业数组) → 工作目录管理(命名、中间状态检测) → 结果汇总(批量提取与清理)。任何批处理脚本逃不出这个模式。

我刚开始写批处理的时候,经常把「模板引擎」和「执行调度」混在一起写。结果代码改一下参数就要改很多地方,很痛苦。后来从工程中学到:把参数、模板、执行、结果四层完全分开,复用性直接上了一个台阶。

3.6 总结一下

批处理不是把多个命令粘在一起就完事了,而是

  • 设计好参数化模板
  • 规划目录结构与中间状态标记
  • 考虑失败重试与日志
  • 最后的自动结果提取

这三个场景——温度扫描、浓度扫描、多副本模拟——几乎覆盖了 80% 的 GROMACS 自动化需求。后面的章节我会带你一步步写出真正能用的批处理脚本,中间会用到很多我今天提到的技巧和避坑经验。