4. 批量计算结果的组织与命名
当计算任务从几个分子扩展到几十、上百个体系时,真正拖慢进度的往往不是算力,而是“找不到文件、分不清版本、覆盖了原始结果”。批量计算的组织问题本质上是可追溯性问题:任何一个最终汇总表里的数字,都必须能沿着一条清晰的路径回溯到它的输入结构、计算参数、原始输出和派生脚本。本节围绕目录结构、任务标识、版本管理和原始/派生文件分离四个抓手,给出一套可直接落地的组织方案。
一、目录结构:按“层级语义”而非“时间顺序”组织
最常见的错误是按提交时间建目录,如 run_0712/、run_0713/。时间目录在任务少时直观,但一旦需要按分子、按方法、按批次筛选,就会陷入反复翻找。推荐采用“项目—体系—方法—任务”四层语义结构,让路径本身携带检索信息:
proj_alkane_2024/
├── 00_input/ # 原始结构、初始构象、输入模板
│ ├── mol_001.xyz
│ ├── mol_002.xyz
│ └── template_opt.inp
├── 10_runs/ # 计算任务(按任务标识命名)
│ ├── T0001_mol001_B3LYP_opt/
│ │ ├── input.inp
│ │ ├── job.log
│ │ ├── job.err
│ │ └── status.json
│ └── T0002_mol002_B3LYP_opt/
├── 20_raw/ # 原始输出归档(只读)
├── 30_derived/ # 派生数据:提取、转换、汇总
│ ├── extract/
│ ├── conformers/
│ └── summary/
├── 40_scripts/ # 提交、解析、汇总脚本
└── 90_meta/ # 任务清单、版本记录、日志
├── task_index.csv
└── CHANGELOG.md
这套结构的核心是:输入、运行、原始输出、派生结果、脚本、元数据各自独立。任何一层都可以单独备份、单独清理、单独版本化,而不会牵连其他层。
二、任务标识:让每个任务有唯一且可解析的 ID
目录名不应只是“好看”,而应能被脚本解析。推荐任务标识采用固定字段拼接,字段之间用下划线分隔,字段内部避免空格和中文:
T{序号}_{分子ID}_{方法}_{任务类型}_{版本}
示例:T0007_mol012_B3LYP_opt_v2
其中:T0007 是全局唯一序号,保证排序稳定;mol012 对应输入结构;B3LYP 是泛函/方法标签;opt 表示几何优化,可扩展为 sp(单点)、freq(频率)、td(激发态)等;v2 是版本号。这样命名的好处是,用一条正则就能从目录名批量提取分子、方法、任务类型,直接生成汇总表的索引列,而不必逐个打开日志。
90_meta/task_index.csv 中维护一张任务总表,字段至少包含:task_id、mol_id、method、task_type、version、submit_time、status、energy、note。目录名与表内 task_id 必须完全一致,避免“目录一套、表里一套”。三、版本管理:区分“输入版本”与“结果版本”
版本管理最容易混淆的是:改了输入模板算不算新版本?重算了同一个任务算不算新版本?建议明确两条规则:
- 输入变更即升版本:只要
input.inp中的泛函、基组、溶剂模型、收敛阈值、初始构象任一发生变化,就必须生成新的任务目录并递增版本号,旧目录保留不动。 - 纯重算不升版本,但保留运行记录:若输入完全一致,仅因集群中断而重跑,可在同一任务目录下用
attempt_01/、attempt_02/区分,最终以成功的那次为准,并在status.json中记录active_attempt。
版本号建议用 v1、v2 递增,而不是日期。日期适合做批次标签,不适合做版本标识,因为同一天可能产生多个版本,且日期无法表达“谁替代谁”。在 CHANGELOG.md 中按版本记录变更原因,例如“v2:将基组由 6-31G(d) 升级为 def2-SVP,因 v1 频率出现虚频”。
四、原始文件与派生文件的分离策略
原始文件指计算程序直接产生的、不可再生的输出,如 job.log、job.err、*.chk、*.wfn。派生文件指由脚本从原始文件提取或转换得到的数据,如能量表、频率表、坐标转换文件、汇总 CSV。分离策略的核心是三条:
- 原始文件只读:计算完成后立即将
10_runs/中成功任务的原始输出复制或硬链接到20_raw/,并设为只读。后续所有解析脚本只读20_raw/,绝不修改。 - 派生文件可重建:
30_derived/下的任何文件都应能由40_scripts/中的脚本从20_raw/重新生成。因此派生目录可以随时删除重建,不承担“唯一副本”的角色。 - 汇总表带溯源列:最终汇总 CSV 中除能量、频率等结果列外,必须包含
task_id和raw_path两列,使每一行都能定位到原始日志。
图中展示了三层分离关系:10_runs 中的成功任务归档到只读的 20_raw;40_scripts 从 20_raw 读取并生成可重建的 30_derived;最终汇总表同时关联 90_meta 中的任务索引,并保留 raw_path 溯源列。虚线表示索引关联,实线表示数据流向。
五、一个可直接套用的任务索引与命名检查清单
在批量提交前,用下面这张检查表逐项确认,可以避免绝大多数“事后找不到”的问题:
| 检查项 | 合格标准 | 常见错误 |
|---|---|---|
| 任务目录名 | 符合 T{序号}_{分子}_{方法}_{类型}_{版本} | 含空格、中文、日期混用 |
| task_index.csv | task_id 与目录名完全一致 | 表内 ID 与目录名不一致 |
| 输入版本 | 参数变更即升版本,旧版保留 | 直接覆盖旧 input.inp |
| 原始输出 | 成功任务已归档到 20_raw 且只读 | 只留在 10_runs 中被误删 |
| 派生数据 | 可由脚本从 20_raw 重建 | 手工修改派生 CSV 后无法复现 |
| 汇总表溯源 | 含 task_id 与 raw_path 列 | 只有能量值,无法回溯 |
把这套结构固定下来后,批量计算的组织工作就从“每次重新想怎么放”变成“按规则填字段”。后续无论是新增分子、切换泛函,还是重跑失败任务,都只是在既有层级上追加目录和索引行,而不会破坏已有结果的可追溯性。真正需要人工判断的,只剩下“哪些任务算成功、哪些结果进入最终筛选”这一层,而这正是下一阶段汇总与筛选要处理的问题。