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。分离策略的核心是三条:

  1. 原始文件只读:计算完成后立即将 10_runs/ 中成功任务的原始输出复制或硬链接到 20_raw/,并设为只读。后续所有解析脚本只读 20_raw/,绝不修改。
  2. 派生文件可重建:30_derived/ 下的任何文件都应能由 40_scripts/ 中的脚本从 20_raw/ 重新生成。因此派生目录可以随时删除重建,不承担“唯一副本”的角色。
  3. 汇总表带溯源列:最终汇总 CSV 中除能量、频率等结果列外,必须包含 task_id 和 raw_path 两列,使每一行都能定位到原始日志。
批量计算结果组织:原始层 → 派生层 → 汇总层 00_input mol_001.xyz template_opt.inp 10_runs T0001_mol001_B3LYP_opt_v1 input.inp / job.log / status.json 20_raw(只读) job.log 归档 *.chk / *.wfn 30_derived(可重建) extract/ 能量提取 conformers/ 构象转换 summary/ 汇总表 40_scripts submit.sh parse_log.py build_summary.py 90_meta task_index.csv CHANGELOG.md 版本记录 最终汇总表 summary.csv task_id | mol_id | method | energy | freq_min | raw_path 每行可回溯到 20_raw 中的原始日志 索引关联 归档 生成

图中展示了三层分离关系:10_runs 中的成功任务归档到只读的 20_raw;40_scripts 从 20_raw 读取并生成可重建的 30_derived;最终汇总表同时关联 90_meta 中的任务索引,并保留 raw_path 溯源列。虚线表示索引关联,实线表示数据流向。

五、一个可直接套用的任务索引与命名检查清单

在批量提交前,用下面这张检查表逐项确认,可以避免绝大多数“事后找不到”的问题:

检查项合格标准常见错误
任务目录名符合 T{序号}_{分子}_{方法}_{类型}_{版本}含空格、中文、日期混用
task_index.csvtask_id 与目录名完全一致表内 ID 与目录名不一致
输入版本参数变更即升版本,旧版保留直接覆盖旧 input.inp
原始输出成功任务已归档到 20_raw 且只读只留在 10_runs 中被误删
派生数据可由脚本从 20_raw 重建手工修改派生 CSV 后无法复现
汇总表溯源含 task_id 与 raw_path 列只有能量值,无法回溯

把这套结构固定下来后,批量计算的组织工作就从“每次重新想怎么放”变成“按规则填字段”。后续无论是新增分子、切换泛函,还是重跑失败任务,都只是在既有层级上追加目录和索引行,而不会破坏已有结果的可追溯性。真正需要人工判断的,只剩下“哪些任务算成功、哪些结果进入最终筛选”这一层,而这正是下一阶段汇总与筛选要处理的问题。