Pipeline de automação · DICE + Gaussian

Workflow de simulação
Monte Carlo + DFT
em solvatação explícita

Documentação técnica: da geometria inicial à análise comparativa de espectros. Inclui monitoramento de jobs, controle de gargalo no cluster, limpeza de scratch e verificação automática de frequências imaginárias. Flexível em métodos, bases, solvatação e fatores de escala.

DICE · Monte Carlo Gaussian · DFT Python · cclib SLURM / PBS UEL · Londrina, PR
01 · Visão geral

Do arquivo de coordenadas
ao espectro comparativo

O workflow encadeia três domínios — mecânica quântica (Gaussian), mecânica estatística (DICE) e análise espectral (Python). A automação conecta essas camadas sem eliminar as decisões científicas.

Manual hoje
Automatizável
Permanece científico
01Manual
Geometria inicial — otimização PM3
O .gjf inicial é submetido para otimização de baixo custo (PM3 ou HF/STO-3G). A geometria convergida é exportada como coords_optimized.xyz — porta de saída para todos os estágios seguintes.
02Auto
Geração de inputs em batch
A partir de coords_optimized.xyz e templates Jinja2, o gerador cria os .gjf para cada combinação método × base × configuração (in vacuo, PCM, SP). Scripts SLURM/PBS gerados junto. Trocar método é editar o config.yaml.
03Auto
Cálculos Gaussian → verificação automática
Jobs submetidos com encadeamento afterok. Após cada opt+freq, o freq_checker verifica frequências imaginárias e dispara rerun com calcfc se necessário. SP e solvente explícito são excluídos da verificação.
04Auto
Extração CHELPG → preparação do DICE
Cargas CHELPG extraídas dos .log e convertidas para parâmetros Lennard-Jones (.dfr + .pot). Caixa de solvatação com condições periódicas e dice.inp montados automaticamente.
05Auto
Simulação Monte Carlo (DICE)
DICE roda como subprocess. Termalização e amostragem configuráveis. Se o processo quebrar, o scratch_cleaner é acionado antes de qualquer resubmissão — o cluster fica limpo para o próximo job.
06Auto
Seleção da concha via RDF / MDDF
Output do DICE processado: RDF ou MDDF calculada, raio de corte configurável (padrão 2,5 Å). Moléculas dentro do corte compõem a concha exportada como shell_N.xyz.
07Auto
Single points com solvente explícito
A concha alimenta .gjf de SP para cada método, gerados pelos mesmos templates. Frequências imaginárias aqui são esperadas e não disparam reruns.
08Auto
Extração, scaling e tabelas
Parser lê todos os .log via cclib: frequências, Raman/IR, SCF, ZPVE, entropia, dipolo, HOMO/LUMO, gap óptico (TD-DFT). Scaling por método aplicado automaticamente. Tabelas .xlsx e .csv para os três ambientes.
09Auto
Espectros comparativos
Broadening Lorentziano/Gaussiano configurável. Sobreposição dos três ambientes por método, com barras de modos vibracionais. PNG 300 dpi e SVG vetorial — sem Origin.
10Científico
Atribuição dos modos vibracionais
Identificação dos shifts por solvatação, correlação com grupos funcionais, interpretação das ligações de hidrogênio. O pipeline entrega dados organizados — a análise é humana.
11Científico
Escrita e submissão do manuscrito
Com figuras prontas, tabelas formatadas e propriedades organizadas, o tempo do pesquisador vai inteiramente para discussão, conclusões e submissão.

02 · Organização

Estrutura de pastas
espelha o workflow

Cada pasta corresponde a um estágio. A numeração força a ordem de execução. O código reutilizável fica separado dos dados — ao aplicar o pipeline para uma nova molécula, copia-se a estrutura de dados, não o código.

filesystem — h2o_pipeline/
gerado pipeline dado cluster saída análise
h2o_pipeline/ — raiz: uma pasta por molécula estudada
├──
01_geometry/ otimização inicial de geometria
  ├──
input/
  │  └──
h2o.gjf gerado PM3 opt
  ├──
output/
  │  ├──
h2o_pm3.log dado
  │  └──
h2o_pm3.chk dado
  ├──
coords_optimized.xyz saída ⟶ porta de saída para estágios seguintes
  └──
job_pm3.sh gerado
├──
02_gaussian_batch/ inputs em batch por método × configuração
  ├──
HF/6-311++G(d,p)/ subpasta por método (flexível via config.yaml)
  │  ├──
in_vacuo.gjf gerado
  │  ├──
pcm.gjf gerado
  │  ├──
sp.gjf gerado
  │  └──
job.sh gerado
  ├──
B3LYP/6-311++G(d,p)/ mesma estrutura
  ├──
CAM-B3LYP/aug-cc-pVDZ/ mesma estrutura
  ├──
M06-2X/6-311++G(d,p)/ mesma estrutura
  ├──
logs/ *.log organizados após conclusão dos jobs
  └──
chelpg/ saída ⟶ cargas CHELPG para o DICE
├──
03_dice/ simulação Monte Carlo
  ├──
fortran_build/ binário separado dos dados
  │  ├──
Makefile
  │  └──
dice binário compilado
  ├──
setup/
  │  ├──
h2o.dfr gerado parâmetros do soluto
  │  ├──
h2o.pot gerado Lennard-Jones a partir de CHELPG
  │  └──
box.inp gerado caixa com condições periódicas
  ├──
run/
  │  ├──
dice.inp gerado
  │  └──
run_dice.sh gerado
  ├──
output_raw/
  │  ├──
dice.out dado
  │  ├──
cfgs/ configurações amostradas pelo MC
  │  └──
rdf.dat dado
  ├──
analysis/
  │  ├──
rdf_plot.py
  │  └──
mddf.py
  └──
shell_selected/
       ├──
last_config.xyz última geometria MC
       └──
shell_N.xyz saída ⟶ porta de saída para estágio 04
├──
04_explicit_solvent/ SP com concha de solvatação explícita
  ├──
HF/
  │  ├──
sp_explicit.gjf gerado montado a partir de shell_N.xyz
  │  ├──
sp_explicit.log dado
  │  └──
job.sh gerado
  ├──
B3LYP/ mesma estrutura
  ├──
CAM-B3LYP/ mesma estrutura
  └──
M06-2X/ mesma estrutura
├──
05_analysis/ parser, espectros, tabelas, propriedades
  ├──
parser/
  │  ├──
parser.py extração via cclib
  │  ├──
extractor.py ZPVE · entropia · dipolo · HOMO/LUMO
  │  └──
scaling.py aplica scaling factors por método
  ├──
spectra/
  │  └──
visualizer.py broadening · sobreposição · PNG/SVG
  ├──
tables/
  │  ├──
freq_scaled.xlsx saída
  │  └──
modes_comparison.csv saída
  ├──
properties/
  │  └──
properties_summary.csv saída ZPVE · ΔH · S · μ · HOMO-LUMO · Eg
  ├──
td_dft/
  │  ├──
uv_vis_spectrum.py UV-Vis via TD-DFT
  │  └──
optical_gap.csv saída
  ├──
figures/ PNG 300 dpi + SVG por método
  └──
report/
       ├──
summary.md
       └──
validation_notes.md
├──
pipeline/ código reutilizável — não misturar com dados
  ├──
generators/
  │  ├──
gen_inputs.py gera .gjf em batch
  │  └──
templates/ Jinja2: opt.gjf.j2 · freq.gjf.j2 · sp.gjf.j2
  ├──
parsers/
  │  ├──
log_parser.py frequências · Raman · IR · energias
  │  └──
dice_parser.py RDF · MDDF · seleção de shell
  ├──
utils/
  │  ├──
freq_checker.py verifica freqs imaginárias + rerun
  │  ├──
job_checker.py Normal termination · SCF failure
  │  └──
scratch_cleaner.py limpeza em caso de falha de processo
  └──
orchestrator/
       └──
run_pipeline.py ponto de entrada único — lê config.yaml
├──
config.yaml TODOS os parâmetros: métodos · bases · escala · scheduler · scratch
├──
README.md
└──
requirements.txt
Princípio de design
Cada estágio tem uma "porta de saída" bem definida: coords_optimized.xyz (01→02), chelpg/ (02→03), shell_N.xyz (03→04). Nenhum estágio lê diretamente de dentro de outro — a automação entre estágios é trivial e o debugging é localizado.

03 · Parâmetros configuráveis

Flexível em métodos,
bases e escala

Toda parametrização do pipeline reside em config.yaml. Trocar de método, adicionar uma função de base, ajustar o fator de escala ou mudar o modelo de solvatação é editar esse arquivo — nenhum código precisa ser tocado.

Métodos DFT / HF
Lista configurável de métodos
O campo methods aceita qualquer funcional reconhecido pelo Gaussian. Adicionar wB97X-D ou MP2 é incluir uma entrada na lista.
MétodoUso típico
HFReferência histórica, scaling 0.909
B3LYPVibracional geral
CAM-B3LYPUV-Vis / transferência de carga
M06-2XInterações não-covalentes
Funções de base
Mapeamento método → base
Cada método tem sua própria base. O mapeamento gera automaticamente a subpasta e o cabeçalho do .gjf.
MétodoBase padrão
HF6-311++G(d,p)
B3LYP6-311++G(d,p)
CAM-B3LYPaug-cc-pVDZ
M06-2X6-311++G(d,p)
Fatores de escala
Scaling por método — editável
Nenhum valor está hardcoded. Atualizar com referências NIST ou Merrick et al. é editar o config.
MétodoFatorFonte
HF0.909Merrick 2007
B3LYP0.967Merrick 2007
CAM-B3LYP0.983NIST
M06-2X0.983NIST
Solvatação
Modelo de solvente configurável
Trocar de água para etanol ou DMSO exige mudar duas linhas. Raio de corte MDDF também configurável.
ParâmetroPadrão
solventwater
pcm_modelPCM | SMD | CPCM
mddf_cutoff2.5 Å
n_molecules1330
config.yaml — estrutura completa
Todos os parâmetros em um só lugar
molecule: h2o
charge: 0  |  multiplicity: 1
solvent: water  |  pcm_model: PCM   # PCM | SMD | CPCM

methods:          # adicionar/remover aqui — sem tocar no código
  - name: HF         | basis: 6-311++G(d,p) | scaling: 0.909
  - name: B3LYP      | basis: 6-311++G(d,p) | scaling: 0.967
  - name: CAM-B3LYP  | basis: aug-cc-pVDZ   | scaling: 0.983
  - name: M06-2X     | basis: 6-311++G(d,p) | scaling: 0.983

freq_check:
  max_cycles: 3
  skip_stages: [sp, sp_explicit, explicit]

dice:
  n_steps_thermalization: 80000 | n_steps_sampling: 80000
  n_molecules_solvent: 1330     | mddf_cutoff: 2.5

scheduler: slurm   # slurm | pbs | local
memory: "8GB"  |  nproc: 8  |  walltime: "12:00:00"
slurm_partition: short

scratch:
  base_dir: /scratch/$USER/
  cleanup_on_fail: true
  keep_chk: false     # .chk são grandes — remover após extração
  notify_email: ""    # opcional: alerta em caso de falha

04 · Decisões de design

A lógica por trás
de cada escolha

O pipeline não é uma sequência de scripts colados — cada decisão de implementação tem uma razão técnica ou científica.

🔧
Templates Jinja2 para .gjf
Em vez de scripts que constroem strings, os inputs são gerados por templates parametrizados. Trocar rota, keywords ou modelo de cavidade é editar o template — não o código. Um template por tipo: opt.gjf.j2, freq.gjf.j2, sp.gjf.j2.
📍
XYZ como porta de saída
A geometria é sempre exportada como .xyz puro, não como .chk. O .chk é binário e depende da versão exata do Gaussian. O .xyz é portável, versionável no git e abre em qualquer visualizador.
🧱
Fortran do DICE intocado
O núcleo Monte Carlo permanece em Fortran — necessário para ~1330 moléculas com condições periódicas. Python atua como orquestrador: prepara, invoca como subprocess, processa. Não há ganho em reescrever o núcleo.
📊
cclib como parser agnóstico
A cclib lê Gaussian, ORCA, NWChem e outros com a mesma API. Adicionar suporte a ORCA no futuro não exige reescrever os parsers. Frequências, Raman, IR, ZPVE, HOMO/LUMO e TD-DFT cobertos nativamente.
Encadeamento afterok
O freq_checker e o orquestrador são submetidos como dependências do job principal. Se detecta problema, submete o rerun — que dispara novo checker. A cadeia se propaga até max_cycles.
📁
pipeline/ separado dos dados
O código fica em pipeline/, os dados em 01_ a 05_. Para uma nova molécula, copia-se a estrutura de dados e ajusta o config.yaml — o código não é duplicado nem modificado.
MDDF vs RDF
A MDDF é preferida à RDF clássica para moléculas com múltiplos sítios de interação porque usa a menor distância entre qualquer par de átomos soluto-solvente — não a distância entre centros de massa. Essencial para moléculas com grupos funcionais em regiões distintas da estrutura.

05 · Orquestração e controle

Cluster sempre trabalhando,
nunca sobrecarregado

A estratégia garante que o computador institucional esteja sempre ocupado com o próximo job disponível, mas que nunca mais de um estágio pesado rode simultaneamente — o gargalo é controlado por dependências de jobs, não por supervisão manual.

Encadeamento por dependência (afterok)
Cada job submete o próximo como dependência condicional — o job seguinte só entra na fila quando o anterior termina com sucesso. Se o job falhar, a cadeia para automaticamente.

afterok — continua somente em sucesso
afternotok — aciona tratamento de falha
afterany — aciona limpeza de scratch sempre
📋
Estados de job monitorados
COMPLETED→ dispara próxima etapa
RUNNINGaguardando — sem ação
PENDINGna fila — sem ação
FAILED→ limpeza scratch + alerta
TIMEOUT→ limpeza scratch + alerta
CANCELLED→ limpeza scratch
🔁
Cadeia completa de submissão — exemplo com HF in vacuo
# 1. Submete o cálculo principal JID_CALC=$(sbatch --parsable job_HF_in_vacuo.sh) # 2. freq_checker roda após sucesso JID_CHECK=$(sbatch --parsable --dependency=afterok:$JID_CALC run_freq_checker.sh) # 3. Limpeza de scratch roda SEMPRE (sucesso ou falha) JID_CLEAN=$(sbatch --parsable --dependency=afterany:$JID_CALC scratch_cleaner.sh) # 4. Próxima etapa só após checker OK JID_NEXT=$(sbatch --parsable --dependency=afterok:$JID_CHECK prepare_dice.sh) # 5. Tratamento de falha sbatch --dependency=afternotok:$JID_CALC handle_failure.sh
Gargalo controlado
O cluster tem recursos compartilhados. A cadeia afterok garante que o estágio 03 (DICE, pesado em memória e I/O) só seja submetido depois que todos os jobs do estágio 02 terminaram com sucesso. O pesquisador não precisa supervisionar — o scheduler controla a ordem.

Limpeza de scratch em caso de falha

O /scratch do cluster é rápido mas finito e compartilhado. Jobs interrompidos deixam arquivos temporários grandes (.chk, Gau-*, arquivos de restart do DICE) que ocupam espaço e podem corromper reruns. O scratch_cleaner.py cuida disso automaticamente.

🔍
Detecta diretório de scratch
$SCRATCH_DIR do ambiente ou do config.yaml. Identifica o subdiretório pelo $SLURM_JOB_ID passado como argumento.
📦
Salva o que é recuperável
Antes de apagar: copia o .log parcial e o último .chk (se configurado) para o diretório de output do projeto. Podem conter geometria útil para resubmissão manual.
🗑
Remove temporários
Apaga: Gau-*, *.rwf, *.d2e, *.int, *.skr, restarts do DICE (*.rst, cfgs/ parciais). Mantém .log e .gjf originais.
📝
Registra no relatório
Grava no pipeline_events.log: timestamp, job ID, estágio, motivo da falha (FAILED / TIMEOUT / CANCELLED) e arquivos removidos.
🔔
Alerta por e-mail (opcional)
Se notify_email estiver no config.yaml, envia um resumo com nome do job, estágio e link para o log de eventos via mail.
Cluster liberado
Scratch limpo. Estado do pipeline gravado no log — retomar é ler o último evento e resubmeter a partir daquele estágio.
# scratch_cleaner.py — chamado via job SLURM ou diretamente python scratch_cleaner.py \ --job_id $SLURM_JOB_ID \ --stage 02_gaussian_batch/HF/in_vacuo \ --reason FAILED \ --save_partial_log # script de limpeza encadeado automaticamente (afterany) #!/bin/bash #SBATCH --job-name=scratch_clean #SBATCH --dependency=afterany:$JID_CALC #SBATCH --time=00:05:00 # detecta o estado do job pai e passa como argumento STATE=$(sacct -j $PARENT_JID --format=State --noheader | head -1 | xargs) python pipeline/utils/scratch_cleaner.py \ --job_id $PARENT_JID \ --config config.yaml \ --reason $STATE
Atenção — scratch vs. projeto
Nunca salvar dados permanentes no scratch. O diretório /scratch pode ser limpo pela administração sem aviso. Todos os .log, .xyz e saídas de análise devem ser escritos diretamente no diretório do projeto ou copiados pelo próprio job antes de terminar.

06 · Controle de qualidade

Verificador de frequências
imaginárias — freq_checker

Uma frequência negativa num cálculo de frequências (não SP) indica que a geometria não convergiu para um mínimo real — o sistema está num ponto de sela. O freq_checker.py automatiza a detecção e a correção.

freq_checker.py — lógica de execução
pipeline/utils/freq_checker.py
max_cycles configurável
Job opt+freq termina — cálculo de frequências concluído pelo Gaussian
Verifica Normal termination no .log — se ausente, status error imediato
Estágio SP ou explícito? → pula. Frequências imaginárias são esperadas nesses sistemas — verificar causaria reruns sem sentido.
Nenhuma frequência negativa → status OK. Cadeia continua para o próximo estágio.
Frequências negativas encontradas → extrai última Standard orientation como geometry_cycleN.xyz. Salvo para inspeção antes de qualquer resubmissão.
Gera novo .gjf com opt=(calcfc,maxcycles=100). O calcfc força cálculo analítico das constantes de força — estratégia padrão para pontos de sela.
Submete o rerun ao scheduler. Novo checker encadeado como afterok do rerun.
Limite de ciclos atingido → status max_cycles. Relatório JSON gerado, log de erro visível. Ação manual necessária.
Por que calcfc e não reotimizar direto?
Sem constantes de força analíticas, o otimizador Berny usa uma hessiana aproximada que pode ser ruim próximo a um ponto de sela — e continuar descendo pelo mesmo caminho errado. O calcfc constrói uma hessiana correta antes de qualquer passo, aumentando muito a probabilidade de convergir para o mínimo real.
# verificação de um .log específico python freq_checker.py \ --log 02_gaussian_batch/HF/in_vacuo.log \ --method HF \ --stage in_vacuo \ --max_cycles 3 # todos os métodos × estágios de uma vez python freq_checker.py --check_all --max_cycles 3 # dry run — simula sem submeter python freq_checker.py --check_all --dry_run # encadeamento — checker dispara automaticamente após o job JID=$(sbatch --parsable job_hf_in_vacuo.sh) sbatch --dependency=afterok:$JID run_freq_checker.sh
Estágios verificados vs. ignorados
O checker verifica in vacuo e PCM. Ignora automaticamente sp, sp_explicit e explicit — frequências imaginárias nesses estágios são consequência esperada da construção do sistema, não um erro de otimização.