ORCA 예제는 ORCA 가이드에 실린 ORCA 6 출력이다. opt.out은 물의 B3LYP-D4/def2-TZVP 구조 최적화 + 진동수 계산이다. QE와 LAMMPS 예제도 각 가이드를 쓰면서 실제로 돌린 출력이다. grep, sed, awk 문법은 앞 장에 정리했다.

ORCA: 마지막 에너지

ORCA는 에너지를 계산할 때마다 FINAL SINGLE POINT ENERGY 줄을 쓴다. 최적화에서는 사이클마다 한 줄씩 나오므로 마지막 줄만 가져와야 한다.

$ grep 'FINAL SINGLE POINT ENERGY' opt.out
FINAL SINGLE POINT ENERGY       -76.426286364586
FINAL SINGLE POINT ENERGY       -76.426353431114
FINAL SINGLE POINT ENERGY       -76.426364583348
FINAL SINGLE POINT ENERGY       -76.426366351564
FINAL SINGLE POINT ENERGY       -76.426369457144
$ grep 'FINAL SINGLE POINT ENERGY' opt.out | tail -1 | awk '{print $5}'
-76.426369457144

awk 하나로도 된다. 맞는 줄마다 변수에 덮어쓰고 END에서 출력하면 마지막 값이 남는다. 줄이 하나도 없으면 빈 줄이 나오므로, 스크립트에서는 결과가 비었는지 확인한다.

$ awk '/FINAL SINGLE POINT ENERGY/ {e = $5} END {print e}' opt.out
-76.426369457144

ORCA: 최적화 사이클별 에너지

수렴 과정을 그림으로 보려면 사이클 번호와 에너지를 두 열로 저장한다. gnuplot이나 matplotlib이 그대로 읽는다.

$ grep 'FINAL SINGLE POINT ENERGY' opt.out | awk '{printf "%d %.10f\n", NR, $5}' > scf.dat
$ cat scf.dat
1 -76.4262863646
2 -76.4263534311
3 -76.4263645833
4 -76.4263663516
5 -76.4263694571

다섯 줄은 최적화 네 사이클과 수렴한 구조에서의 마지막 계산이다. 마지막 값 기준 상대 에너지(kcal/mol)를 셋째 열로 붙이면 수렴이 얼마나 남았는지 한눈에 보인다. 모든 값을 배열에 모은 뒤 END에서 계산한다.

$ awk '/FINAL SINGLE POINT ENERGY/ {n++; e[n] = $5}
       END {for (i = 1; i <= n; i++) printf "%d %.10f %8.4f\n", i, e[i], (e[i] - e[n]) * 627.5095}' opt.out
1 -76.4262863646   0.0521
2 -76.4263534311   0.0101
3 -76.4263645833   0.0031
4 -76.4263663516   0.0019
5 -76.4263694571   0.0000

ORCA: 정상 종료와 수렴 확인

값을 쓰기 전에 계산이 끝까지 돌았는지부터 본다. 정상 종료한 ORCA 출력의 마지막 부분에는 ORCA TERMINATED NORMALLY가 있고, 최적화가 수렴하면 THE OPTIMIZATION HAS CONVERGED가 찍힌다. 05-killed는 비교를 위해 출력 앞부분만 남겨 walltime에 걸려 죽은 계산처럼 만든 파일이다.

$ grep -c 'ORCA TERMINATED NORMALLY' opt.out
1
$ grep -c 'ORCA TERMINATED NORMALLY' jobs/05-killed/water.out
0
$ grep -q 'THE OPTIMIZATION HAS CONVERGED' opt.out && echo converged || echo 'NOT converged'
converged

grep -q는 아무것도 출력하지 않고 종료 코드만 돌려준다. 찾으면 0, 못 찾으면 1이라 if에 바로 쓴다.

ORCA: 허수 진동수와 자유 에너지

진동수는 VIBRATIONAL FREQUENCIES와 NORMAL MODES 사이에 있다. awk의 범위 패턴 /시작/,/끝/으로 그 구간만 잘라 낸 뒤 둘째 열을 본다.

$ awk '/VIBRATIONAL FREQUENCIES/,/NORMAL MODES/' opt.out | awk '/cm\*\*-1/ {print $2}'
0.00
0.00
0.00
0.00
0.00
0.00
1615.68
3780.78
3885.42

처음 여섯 개의 0은 병진과 회전이다. 허수 진동수는 ORCA가 음수로 쓰고 뒤에 ***imaginary mode***를 붙인다. 아래 명령은 구간 안에서 음수인 줄만 세어 출력한다. ts.out은 형식을 보이려고 6번 모드를 ORCA 가이드의 예시처럼 음수로 고친 사본이다.

$ awk '/VIBRATIONAL FREQUENCIES/ {f = 1} /NORMAL MODES/ {f = 0}
       f && /cm\*\*-1/ && $2 < 0 {n++; print}
       END {print n + 0, "imaginary"}' opt.out
0 imaginary
$ awk '/VIBRATIONAL FREQUENCIES/ {f = 1} /NORMAL MODES/ {f = 0}
       f && /cm\*\*-1/ && $2 < 0 {n++; print}
       END {print n + 0, "imaginary"}' ts.out
     6:    -456.78 cm**-1   ***imaginary mode***
1 imaginary

n + 0은 한 번도 증가하지 않은 변수를 빈 문자열이 아닌 0으로 출력하게 한다. 열화학 결과는 이름이 붙은 줄 하나라 grep과 awk로 바로 뽑힌다.

$ grep 'Final Gibbs free energy' opt.out
Final Gibbs free energy         ...    -76.42286885 Eh
$ awk '/Final Gibbs free energy/ {print $6}' opt.out
-76.42286885

ORCA: 마지막 구조를 xyz로

CARTESIAN COORDINATES (ANGSTROEM) 블록은 사이클마다 나온다. 머리글 다음 줄(-----)을 건너뛰고 빈 줄이 나올 때까지 좌표를 모은다. 새 블록을 만나면 개수를 0으로 되돌리므로 끝에는 마지막 블록만 남는다.

awk '/CARTESIAN COORDINATES \(ANGSTROEM\)/ {getline; n = 0; blk = 1; next}
     blk && NF == 0 {blk = 0}
     blk {line[++n] = $0}
     END {print n; print "final geometry"; for (i = 1; i <= n; i++) print line[i]}' opt.out > final.xyz
$ cat final.xyz
3
final geometry
  O      0.000000    0.000000    0.111489
  H      0.000000    0.765189   -0.473160
  H      0.000000   -0.765189   -0.473160

정규식 안의 괄호는 \(로 이스케이프한다. getline은 다음 줄을 읽어 버리는 명령이다. ORCA는 최적화가 끝나면 같은 구조를 입력이름.xyz로도 저장하니, 출력 파일만 남은 경우에 이 방법을 쓴다.

여러 계산을 CSV 표로

계산마다 디렉터리가 하나씩 있고 그 안에 출력이 있다고 하자. 끝나지 않은 계산은 건너뛰고, 나머지의 에너지를 모은 뒤 가장 낮은 값 기준 상대 에너지(1 Eh = 627.5095 kcal/mol)를 붙인다.

#!/bin/bash
# collect.sh: final energy of every job directory into a CSV
set -euo pipefail

echo "name,energy_Eh" > energies.csv
for out in jobs/*/*.out; do
    name=$(basename "$(dirname "$out")")
    if ! grep -q 'ORCA TERMINATED NORMALLY' "$out"; then
        echo "skip $name: not finished" >&2
        continue
    fi
    e=$(awk '/FINAL SINGLE POINT ENERGY/ {e = $5} END {print e}' "$out")
    echo "$name,$e" >> energies.csv
done

awk -F, 'NR > 1 {name[NR] = $1; e[NR] = $2; if (NR == 2 || $2 < min) min = $2}
         END {print "name,energy_Eh,rel_kcal"
              for (i = 2; i <= NR; i++) printf "%s,%.8f,%.2f\n", name[i], e[i], (e[i] - min) * 627.5095}' \
    energies.csv > table.csv
$ bash collect.sh
skip 05-killed: not finished
$ column -t -s, table.csv
name              energy_Eh     rel_kcal
01-single-point   -76.32127441  65.95
02-opt-freq       -76.42636946  0.00
03-dlpno-ccsdt    -76.33195934  59.24
04-solvation-smd  -76.33297155  58.61

여기 쓴 네 계산은 같은 물 분자를 서로 다른 방법과 기저로 계산한 것이라, 상대 에너지 자체에는 화학적 의미가 없다. 의미가 있는 비교는 같은 B3LYP/def2-SVP에서 기체상(01)과 SMD 용매(04)를 비교한 −7.34 kcal/mol 하나다. 실제로는 같은 방법으로 계산한 이성질체나 배좌 디렉터리에 이 스크립트를 돌린다.

Windows에서 온 파일

Windows에서 만들거나 옮긴 파일은 줄 끝이 CRLF(\r\n)일 수 있다. 그러면 마지막 열에 \r이 붙어 CSV에 보이지 않는 문자가 섞이고 $ 앵커가 맞지 않는다. cat -A 파일로 줄 끝에 ^M이 보이면 tr -d '\r' < 원본 > 새파일이나 dos2unix로 고친다.

Quantum ESPRESSO

pw.x는 SCF 반복마다 total energy를 쓰고, 수렴한 값에만 줄 맨 앞에 !를 붙인다. 그래서 ^!로 찾으면 중간값이 섞이지 않는다. 아래는 QE 가이드의 실리콘 SCF 예제 출력이다. 단위는 Ry이고 1 Ry = 13.605693 eV다.

$ grep '^!' si.scf.out
!    total energy              =     -93.45273690 Ry
$ grep '^!' si.scf.out | tail -1 | awk '{printf "%.8f Ry = %.6f eV\n", $5, $5 * 13.605693}'
-93.45273690 Ry = -1271.489248 eV
$ grep -c 'convergence has been achieved' si.scf.out
1
$ grep -c 'convergence NOT achieved' si.scf.out
0
$ grep 'JOB DONE' si.scf.out
   JOB DONE.

vc-relax는 이온 스텝마다 SCF를 새로 돌리므로 ! 줄이 여러 개다. 압력은 P= 줄의 마지막 열이다.

$ grep '^!' si.vcrelax.out | awk '{printf "%d %.8f\n", NR, $5}'
1 -93.44443849
2 -93.45157833
3 -93.45321788
4 -93.45325068
5 -93.45325084
6 -93.45327391
$ grep 'P=' si.vcrelax.out | awk '{print $NF}'
108.71
43.06
5.66
0.39
0.00
-0.03
$ grep -E 'bfgs converged|Final enthalpy' si.vcrelax.out
     bfgs converged in   5 scf cycles and   4 bfgs steps
     Final enthalpy           =     -93.4532508364 Ry

여섯째 값은 BFGS가 끝난 뒤 최종 셀에서 다시 돌린 SCF다. 셀이 바뀌면 평면파 기저도 바뀌므로 값이 조금 다르다. 최적화 결과로는 Final enthalpy를 쓰고, 마지막 SCF와 차이가 크면 컷오프가 부족하다는 신호로 본다.

LAMMPS thermo 표

log.lammps에서 thermo 출력은 Step으로 시작하는 머리글 줄과 Loop time 줄 사이에 있다. run 명령이 여러 번이면 이 구간도 여러 번 나온다. 아래는 LAMMPS 가이드의 Cu 슬랩 예제 로그로, 평형 10000 스텝과 생산 5000 스텝 두 구간이 있다.

$ awk '$1 == "Step" {on = 1; next} /^Loop time/ {on = 0} on' log.lammps | head -4
         0   253.8184      -5749.9163     -5695.3556     -5778.9907    
       500   253.05769     -5694.4003     -5640.0031     -9546.1221    
      1000   267.19235     -5695.7562     -5638.3206      1729.171     
      1500   253.1248      -5692.6357     -5638.2241     -9774.9528    

머리글은 앞에 공백이 있어서 /^Step/으로는 걸리지 않는다. 첫 열을 비교하는 $1 == "Step"이 공백에 상관없이 맞는다. 머리글을 한 번만 # 주석으로 남기면 numpy의 loadtxt나 gnuplot이 바로 읽는 파일이 된다.

$ awk '$1 == "Step" {on = 1; if (!hdr++) print "#", $0; next} /^Loop time/ {on = 0} on' log.lammps > thermo.dat
$ head -3 thermo.dat
#    Step          Temp          PotEng         TotEng         Press     
         0   253.8184      -5749.9163     -5695.3556     -5778.9907    
       500   253.05769     -5694.4003     -5640.0031     -9546.1221    

두 구간이 이어 붙어서 스텝 번호가 0부터 다시 시작한다(이 입력은 생산 전에 reset_timestep 0을 했다). 생산 구간만 필요하면 머리글을 셀 때 두 번째 구간에서만 켠다.

$ awk '$1 == "Step" {run++; on = (run == 2); next} /^Loop time/ {on = 0} on' log.lammps | head -3
         0   247.4071      -5695.3154     -5642.1329     -4114.9433    
       500   254.87546     -5696.5557     -5641.7678     -4072.5059    
      1000   242.9701      -5696.8721     -5644.6433     -4897.1177    

xtb

xtb는 요약 상자 두 곳에 같은 에너지를 쓴다. 아래는 xtb 6.7.1로 물의 GFN2-xTB 단일점(xtb_sp.out)과 메탄올 구조 최적화(xtb_opt.out)를 계산한 출력이다. TOTAL ENERGY 줄은 마지막에 한 번만 나오므로 최적화 출력에서도 최종값이다.

$ grep -E ':: total energy|TOTAL ENERGY' xtb_sp.out
         :: total energy              -5.070222286727 Eh    ::
          | TOTAL ENERGY               -5.070222286727 Eh   |
$ awk '/TOTAL ENERGY/ {print $4}' xtb_opt.out
-8.226118421023
$ grep 'GEOMETRY OPTIMIZATION CONVERGED' xtb_opt.out
   *** GEOMETRY OPTIMIZATION CONVERGED AFTER 5 ITERATIONS ***

xtb는 normal termination of xtb를 표준 출력이 아니라 표준 오류로 쓴다. xtb mol.xyz > sp.out으로만 저장하면 이 줄이 파일에 없으니, 종료 확인에 쓰려면 > sp.out 2>&1로 함께 저장한다.

Python으로 넘어갈 때

grep과 awk는 값 한두 개를 뽑는 데 알맞다. 여러 블록에 걸친 값(진동 모드 벡터, 원자별 전하, 궤도 에너지)이나 프로그램 버전마다 형식이 바뀌는 부분을 다뤄야 한다면 파서 라이브러리를 쓰는 편이 덜 깨진다. cclib은 ORCA, Gaussian 등 양자화학 출력을 읽어 numpy 배열로 주고, ASE의 ase.io.read는 QE 출력과 LAMMPS 덤프를 포함해 구조 파일 대부분을 읽는다.