4. 输出文件解析自动化

计算化学任务的输出文件是整条自动化流水线中最“脏”的数据源。以 Gaussian 为例,一个正常结束的优化任务输出可能包含数百行中间步骤、几十次 SCF 迭代、多个几何构型以及最终的频率分析;而一个异常终止的任务,可能只留下半截 SCF 能量、一段报错信息,甚至因为磁盘配额耗尽而截断在任意一行。解析器的核心任务,不是“读文件”,而是在结构不确定、内容不完整、格式有变体的前提下,稳定地提取出可判定的关键结果。

本章围绕四类关键结果展开:能量、梯度、频率、收敛信息,并补充布居分析等衍生量。重点不在正则表达式本身,而在于如何组织解析逻辑,使其对多任务输出和异常终止具备鲁棒性。

一、关键结果的定位策略:锚点而非行号

最脆弱的解析方式是依赖固定行号或固定行数。输出文件会因基组、溶剂模型、并行核数、版本差异而变化,行号几乎必然漂移。可靠的做法是用语义锚点定位:每一类结果都有其稳定的“标志性字符串”,解析器先扫描锚点,再在锚点附近提取数值。

例如,SCF 收敛后的能量通常出现在 SCF Done: 之后;优化收敛判据出现在 Converged? 或 Item Value Threshold Converged? 表格中;频率结果出现在 Frequencies -- 行;布居分析出现在 Mulliken charges: 或 Natural Population Analysis 之后。解析器应维护一张“锚点—提取规则”映射表,而不是把逻辑写死在流程里。

输出文件鲁棒解析:锚点定位 → 分段提取 → 状态判定 原始输出文件 Entering Link 1 # opt freq b3lyp/6-31g SCF Done: E = -234.5 SCF Done: E = -234.6 Converged? YES Frequencies -- 312.4 Frequencies -- 1045.2 Mulliken charges: 1 C -0.312 2 H 0.156 ... Error termination via link 9999 Normal termination of Gaussian 锚点扫描器 SCF Done: Converged? Frequencies -- Mulliken charges: Error termination Normal termination 按锚点切分输出流 每段独立提取 结构化结果与状态判定 能量 E_final = -234.6 Hartree ✓ 已提取 梯度 / 收敛 RMS Force = 0.00012 < 0.0003 ✓ 已收敛 频率 312.4, 1045.2, ... (3N-6) ✓ 无虚频 布居分析 C: -0.312, H: +0.156 ✓ 已提取 异常终止 Error termination via link 9999 ✗ 标记失败 任务状态 部分成功:能量/频率有效,几何未收敛 锚点定位使解析器对行号漂移、版本差异和截断输出保持稳定

二、能量与梯度的提取:区分中间值与最终值

一个优化任务会输出多次 SCF Done:,每次对应一个几何构型下的单点能。解析器必须区分“中间能量”和“最终能量”。常见做法是:记录所有匹配到的能量值,取最后一个作为当前任务最终能量;同时保留能量序列,用于绘制优化收敛曲线。

梯度信息通常出现在收敛判据表格中,包含四个关键量:Maximum Force、RMS Force、Maximum Displacement、RMS Displacement。每个量都有对应的阈值。解析器应提取这四个数值及其是否收敛的标记,而不是只取一个“YES/NO”。因为在实际排障中,经常出现“力已收敛但位移未收敛”的情况,只记录总状态会丢失诊断信息。

对于频率任务,Frequencies -- 行可能跨多行排列,每行三个频率值。解析器需要按行扫描并累积,直到遇到非频率行。最终频率数量应等于 3N-6(非线性分子)或 3N-5(线性分子)。若数量不符,说明输出被截断或任务异常,应标记为“频率不完整”。

三、收敛信息的结构化:从文本表格到判定字段

收敛判据表格是优化任务中最值得结构化的部分。一个典型的收敛表格包含四行,每行有“当前值”“阈值”“是否收敛”三列。解析器应将其转换为如下结构:

{
  "max_force":       {"value": 0.000045, "threshold": 0.000450, "converged": true},
  "rms_force":       {"value": 0.000012, "threshold": 0.000300, "converged": true},
  "max_displacement":{"value": 0.001200, "threshold": 0.001800, "converged": true},
  "rms_displacement":{"value": 0.000400, "threshold": 0.001200, "converged": true},
  "overall_converged": true
}

这种结构使后续判断不再依赖字符串匹配,而是直接读取布尔字段。当任务未收敛时,还能进一步分析是哪一项拖后腿,为自动调整优化策略提供依据。

四、异常终止的识别与鲁棒处理

异常终止是自动化流水线中最常见的失败模式。Gaussian 的异常终止通常以 Error termination 开头,后跟具体原因,如 via link 9999(优化未收敛)、Disk quota exceeded、Error in internal coordinate 等。解析器应捕获这些行,并提取错误类型。

鲁棒解析的关键在于:即使任务异常终止,也要尽可能提取已产生的有效结果。例如,一个优化任务在最后一步失败,但前面几十步的能量和梯度都是有效的,这些数据对分析收敛行为仍有价值。因此,解析器不应在遇到 Error termination 时直接抛出异常并丢弃全部结果,而应采用“分段提取、逐段标记”的策略:每个锚点段独立解析,成功则记录,失败则标记为缺失,最后汇总任务状态。

实践建议:为每个解析结果附加 source_line 和 parse_status 字段。当后续发现数值异常时,可以快速回溯到原始输出行,判断是解析错误还是计算本身的问题。

五、多任务输出与批量解析

在实际项目中,一个输出文件可能包含多个任务(如先优化、再频率、再单点能),也可能一个目录下有数百个输出文件。解析器需要支持两种模式:单文件多任务解析和批量文件解析。

单文件多任务解析的关键是识别任务边界。Gaussian 输出中,每个任务通常以 Entering Link 1 或 # 开头的路由段为起点,以 Normal termination 或 Error termination 为终点。解析器应按这些边界切分任务块,再对每个块独立提取结果。

批量解析则应采用“先扫描、后提取”的两阶段策略:第一阶段快速扫描所有文件,记录每个文件的锚点存在情况和终止状态;第二阶段只对有效文件进行完整提取。这样可以避免在明显失败的文件上浪费解析时间,也便于生成批量任务的成功率统计。

六、布居分析与衍生量

布居分析(Mulliken、NPA、Hirshfeld 等)的输出通常是一个原子编号、元素符号、电荷值的列表。解析器应将其转换为以原子索引为键的字典,便于后续按元素类型汇总或与几何结构关联。需要注意的是,不同布居分析方法的输出格式不同,锚点也不同,解析器应支持按方法注册不同的提取规则。

此外,偶极矩、极化率、热力学量(零点能、焓、吉布斯自由能)等也常出现在频率任务输出中。这些量通常有固定的锚点,如 Dipole moment、Zero-point correction、Sum of electronic and thermal Free Energies。解析器可以按需扩展,但应保持“锚点—提取规则”的统一注册机制,避免每新增一个量就修改核心解析逻辑。

七、解析结果的质量校验

提取到数值并不等于解析成功。一个负责任的解析器还应做基本校验:能量是否为合理数量级(如 -100 到 -5000 Hartree)、频率是否全为正(对于极小点)、收敛表格四行是否齐全、布居电荷总和是否接近体系总电荷。这些校验可以捕获解析错误,也能发现计算本身的异常。

当校验失败时,解析器应输出明确的警告信息,而不是静默返回错误数值。例如,若频率数量少于 3N-6,应标记 frequency_incomplete;若布居电荷总和偏离总电荷超过 0.01,应标记 charge_sum_mismatch。这些标记可以直接用于后续的任务筛选和人工复核。

核心原则:解析器的目标不是“读到一个数”,而是“给出一个可判定、可追溯、可批量处理的结构化结果”。锚点定位保证稳定性,分段提取保证鲁棒性,质量校验保证可信度,三者缺一不可。