3. 输入文件生成自动化
计算化学任务的自动化,第一步往往不是提交作业,而是把输入文件稳定、可复现地生成出来。Gaussian 的 .gjf、ORCA 的 .inp、VASP 的 INCAR/POSCAR/KPOINTS、LAMMPS 的 in 脚本,格式差异极大,但它们的生成逻辑高度一致:固定骨架 + 可变参数 + 批量组合。本节围绕这条主线,讲清楚如何用模板引擎把输入文件生成从“手工复制粘贴”变成“参数驱动的流水线”。
一、把输入文件拆成“骨架”与“变量”
任何输入文件都可以拆成两部分:不随任务变化的结构骨架,以及随体系、方法、精度变化的参数。以 Gaussian 单点能计算为例,骨架是路由段、电荷多重度行、坐标块和结尾空行;变量则是泛函、基组、电荷、多重度、坐标。ORCA 的骨架是 ! 关键词行、%pal 并行块、* xyz 坐标段;VASP 的骨架是 INCAR 的标签行、POSCAR 的晶格与原子行、KPOINTS 的网格行。
模板化的关键动作,是把骨架写成带占位符的模板文件,把变量抽成一份结构化参数表。这样同一套模板可以服务成百上千个任务,而参数表就是任务清单本身。
# gaussian_sp_template.gjf
%chk={{jobname}}.chk
%mem={{mem}}
%nprocshared={{nproc}}
#p {{method}}/{{basis}} {{jobtype}}
{{title}}
{{charge}} {{multiplicity}}
{{coords}}
模板中的 {{...}} 是占位符,替换时由参数表提供值。注意 %chk 用 jobname 生成,保证每个任务有独立检查点文件,避免并行任务互相覆盖。
二、参数替换:从字典到文件
参数替换的本质是“键值映射 + 文本渲染”。用 Python 的 string.Template 或 Jinja2 都可以,前者轻量,后者支持条件与循环。下面是一个最小可用的替换函数,同时处理 Gaussian 与 ORCA 两种模板:
from string import Template
import itertools, os
def render(tpl_path, params, out_path):
with open(tpl_path) as f:
tpl = Template(f.read())
text = tpl.safe_substitute(params)
os.makedirs(os.path.dirname(out_path), exist_ok=True)
with open(out_path, "w") as f:
f.write(text)
return out_path
# 参数表:每个键对应模板占位符
base = {
"jobname": "water_sp",
"mem": "8GB",
"nproc": "8",
"method": "B3LYP",
"basis": "def2-TZVP",
"jobtype": "sp",
"title": "water single point",
"charge": "0",
"multiplicity": "1",
"coords": "O 0.000 0.000 0.117\nH 0.000 0.757 -0.469\nH 0.000 -0.757 -0.469"
}
render("gaussian_sp_template.gjf", base, "runs/water_sp/water_sp.gjf")
这里有两个工程细节值得强调。第一,safe_substitute 在占位符缺失时保留原样而不报错,适合调试;生产环境建议改用严格替换,让缺失参数尽早暴露。第二,坐标块是多行文本,替换时要保证缩进和换行不被破坏,因此模板中坐标占位符应独占一行,替换值内部自带换行。
三、批量组合扫描:笛卡尔积与命名
批量扫描的核心是参数组合。假设我们要对水分子做泛函与基组的二维扫描,泛函取 B3LYP、PBE0、M06-2X,基组取 6-31G*、def2-SVP、def2-TZVP,共 9 个任务。用 itertools.product 生成笛卡尔积,并为每个组合生成唯一任务名:
methods = ["B3LYP", "PBE0", "M06-2X"]
bases = ["6-31G*", "def2-SVP", "def2-TZVP"]
for m, b in itertools.product(methods, bases):
tag = f"{m}_{b}".replace("*", "s").replace("-", "")
p = dict(base)
p.update({"method": m, "basis": b, "jobname": tag})
render("gaussian_sp_template.gjf", p, f"runs/{tag}/{tag}.gjf")
命名规则必须可逆、可读、无非法字符。把 * 替换为 s、去掉 -,是为了避免文件名在跨平台时出问题。任务名同时写入 %chk,保证检查点文件与任务一一对应。
四、多程序适配:同一参数表,不同模板
真正的自动化价值在于“一套参数、多程序输出”。同一组分子坐标,可以同时生成 Gaussian、ORCA、VASP、LAMMPS 的输入。做法是为每个程序准备独立模板,但共享同一份参数表。下图展示了这一数据流:参数表作为唯一数据源,经模板渲染后分发到四个程序的输入文件,再进入各自的批量扫描目录。
图中可以看到,参数表是唯一数据源,模板引擎负责替换与组合,四个程序模板各自独立,输出目录按“方法_基组”命名。这种结构让新增一个程序只需增加一个模板,新增一个扫描维度只需在参数表里加一个列表。
五、VASP 与 LAMMPS 的特殊处理
VASP 的输入是多个文件,模板化时要分别处理。INCAR 是标签行,适合键值替换;POSCAR 的晶格与坐标是结构化文本,建议用专门的坐标生成函数而不是纯文本替换;KPOINTS 的网格密度常随晶胞尺寸变化,需要根据 k-spacing 计算。LAMMPS 的 in 脚本包含变量、势函数路径、温度循环,模板中要保留 variable 与 run 结构,只替换体系名、势文件、温度点。
一个常见错误是把 VASP 的 POSCAR 直接当纯文本模板,结果坐标行数变化时替换错位。正确做法是:模板只保留标题行与晶格行,原子坐标由脚本按元素分组生成,再拼接进模板。LAMMPS 则要注意势函数文件路径必须用绝对路径或相对于运行目录的路径,否则批量提交时容易找不到文件。
六、可复现性与校验
生成完输入文件后,必须做两件事:一是记录生成参数,二是校验文件完整性。建议在每个任务目录下写入一份 params.json,保存该任务的全部参数,这样即使模板后续修改,也能追溯当时生成的条件。校验方面,Gaussian 检查 %chk 与 #p 行是否存在,ORCA 检查 ! 行与坐标段,VASP 检查 INCAR 关键标签与 POSCAR 原子数是否匹配,LAMMPS 检查势文件路径是否存在。