Good practices

A short catalog of working conventions that aren’t obvious if you’ve never operated a quantum-chemistry program from a clone. None of these are vibe-qc-specific — they apply to running Gaussian / ORCA / NWChem / PySCF / CP2K just as much — but nobody tells you, and everybody learns the hard way. Read this once before you start.

File organisation

One project = one directory, outside the repo. Vibe-qc writes its outputs (.out, .molden, .traj, .cube, .xsf) into the current working directory. If you run inside the cloned source tree, those files land next to cpp/ and python/ — at best clutter, at worst a git clean away from being deleted.

The convention used throughout these docs:

~/vibeqc-runs/
├── water-pbe/
│   ├── water.py
│   ├── water.out
│   ├── water.molden
│   └── water.traj
├── h2o-trimer-mp2/
│   └── ...
└── lih-bulk-bands/
    └── ...

Geometries in a sibling directory if you reuse them across methods — saves you from cp h2o.xyz ../next-project/ every time you start a comparison:

~/vibeqc-runs/
├── geometries/
│   ├── h2o.xyz
│   ├── glycine.xyz
│   └── lih_4.5bohr.cif
├── water-pbe/
└── water-b3lyp/

The .out file is the record. It carries the runtime banner: vibe-qc version, git revision, dirty-tree flag, native-library versions. Don’t delete .out files for runs you might cite later — they’re your provenance trail.

Naming

Convention: <system>_<method>_<basis>.py. Six months later you can ls | grep glycine and find every variant you ran:

glycine_rhf_631gss.py
glycine_rks_pbe_def2tzvp.py
glycine_uks_b3lyp_def2tzvp.py
glycine_mp2_ccpvdz.py

Match the output= argument so the side-effect files line up:

run_job(mol, basis="def2-tzvp", method="rks", functional="PBE",
        output="glycine_rks_pbe_def2tzvp")
# produces: glycine_rks_pbe_def2tzvp.out
#           glycine_rks_pbe_def2tzvp.molden

For periodic runs, encode the k-mesh too: lih_rks_pbe_sto3g_k4x4x4.py.

Reproducibility

Pin a tagged version for any calculation going into a paper. git checkout v0.4.6 (or whichever release you’re targeting) before running setup_native_deps.sh and pip install. The banner will then read Release v0.4.6 instead of the moving main target, and your numbers stay reproducible if a future commit changes a default. See picking a build to test against for the full workflow.

Version-control the input script alongside the manuscript draft. Treat glycine_mp2_ccpvdz.py like Methods-section text — it is the methods section, in executable form.

Save the banner. Every .out file starts with a labeled box recording vibe-qc version, codename, git revision, dirty-tree flag, and linked native-library versions:

╔════════════════════════════════════════════════════════════════════════════════╗
║ Release v0.5.0 "Wilson's Otter"  —  Quantum chemistry for molecules and solids ║
║ © Michael F. Peintinger · MPL 2.0  ·  https://vibe-qc.com                      ║
║ linked: libint 2.13.1 · libxc 7.0.0 · spglib 2.7.0                             ║
╚════════════════════════════════════════════════════════════════════════════════╝

That single block is your provenance line — don’t strip it when copy-pasting into a SI appendix. The version + git revision + linked library versions together pin the exact binary that produced the numbers, down to the libint / libxc / spglib commits.

If you used uncommitted local changes, the banner will read dirty (between the SHA and the closing parenthesis). Don’t ship dirty-tree numbers in a paper. Either commit + tag and re-run, or document the diff in your SI.

See example_outputs for full reference outputs from canonical calculations — the banner above is excerpted from the H₂O RHF reference run.

Performance hygiene

Always set OMP_NUM_THREADS explicitly. OpenMP defaults to “every core on the machine,” which is rude on shared nodes and pessimal on workstations with hyperthreaded cores (you usually want physical cores, not logical):

OMP_NUM_THREADS=4 ~/path/to/vibeqc/.venv/bin/python water.py

Estimate memory before a large run. Vibe-qc has a pre-flight memory budget estimator — it’ll tell you the integral-storage footprint before the SCF starts, so you don’t OOM 30 minutes in. See the memory user guide.

Smallest basis that answers the question. DZ before TZ before QZ. Run a basis-convergence sweep on a small representative system (5-10 minutes) before committing to a 12-hour QZ production run. Same for k-meshes (3×3×3 → 6×6×6 → 8×8×8) and DFT grids — each parameter has a noise floor, find it once per project.

Trusting a number

Converge each parameter separately, in order. Basis → integration grid → k-mesh → SCF threshold. Verify each is at the noise floor (energy change < 1 mEh per step-up) before trusting the next parameter up. Skip this and you have no idea which parameter your final number is sensitive to.

Cross-check against an established code. Vibe-qc’s molecular stack is validated against PySCF to machine precision in CI — you can do the same in your science. Pick a small variant of your real system, run it through both, and check agreement before extending vibe-qc to a regime where PySCF can’t follow (periodic, large-system, novel methods).

Sanity-check the symmetry. If you set up a high-symmetry system but the SCF result has a tiny dipole or a spurious splitting, your input geometry probably has noise in the last few decimals. Snap to the symmetry first, then run.

Long-running calculations

Always capture stdout to a logfile. SSH connections drop; terminal scrollback is finite. tee keeps a copy on disk alongside the run:

~/path/to/vibeqc/.venv/bin/python water.py 2>&1 | tee water.log

For anything over 10 minutes, detach. nohup is the minimum:

nohup ~/path/to/vibeqc/.venv/bin/python water.py > water.log 2>&1 &

For multi-day runs use screen or tmux so you can reattach and see the live output:

screen -dmS mycalc ~/path/to/vibeqc/.venv/bin/python water.py
screen -r mycalc          # re-attach later
# Ctrl-A, D to detach again

See the running guide for the SSH workflow and the eventual JQ1 personal-job-queue story.

When SCF diverges

The first instinct is to crank tolerances. Almost always wrong. The right instinct is simplify until something converges, then add complexity back one step at a time:

  1. Halve the basis (TZ → DZ, or DZ → STO-3G). If DZ converges and TZ doesn’t, you have a basis-set problem (linear dependency, diffuse-function instability) — try tighter canonical-orthogonalisation thresholds.

  2. Simplify the system. Drop a substituent. Shrink the cell. Get something converging at all, then re-introduce complexity.

  3. Try the SAD initial guess before the core-Hamiltonian guess. SAD (superposition of atomic densities) is much closer to the SCF solution for most molecules.

  4. Bump level_shift to 0.4 Ha for small-gap systems — stabilises occ/vir ordering during early iterations.

  5. Switch on Fermi-Dirac smearing for metals or near-metallic systems. smearing_temperature_K = 300 is a reasonable default.

  6. Engage the quadratic SCF fallback (Phase C1c, v0.5+). Set quadratic_fallback_iter = 20 to switch from diagonalize-F to a Newton step in MO space after 20 stalled standard iterations. See the SCF convergence user guide.

The order matters: try (1)-(2) before (3)-(6). A method-side fix on a fundamentally pathological input just buries the problem.

Common gotchas

Symptom

Likely cause

Fix

ModuleNotFoundError: No module named 'vibeqc'

Ran the wrong Python (system, not venv)

Use ~/path/to/vibeqc/.venv/bin/python or activate the venv. See running.

.venv/bin/python: no such file or directory

Ran from outside the repo with the relative-path shorthand

Use the absolute path; the venv lives where you cloned.

Wildly wrong energy (factor-of-2-off, or sign-flipped)

Bohr vs Ångström unit confusion in coordinates

Vibe-qc internals are bohr; pass Ångström via Molecule.from_xyz().

Wrong number of electrons

Charge / multiplicity mismatch

print(mol.n_electrons()) — does it match what you expect?

.cube won’t open in Avogadro

File is 0 bytes (disk filled during write)

df -h ~ and ls -la *.cube.

Could not find Libxc / Libint2 / FFTW3 at import

Vendored install missing or interrupted

Re-run ./scripts/setup_native_deps.sh — finished deps short-circuit.

SCF energy oscillates between two values

DIIS pathology on near-degenerate occ/vir

Bump level_shift to 0.4, or engage C1c quadratic fallback.

dirty flag in your output banner

Local uncommitted changes

Either commit + tag, or document the diff in your SI before publishing.

Backups

The boring rule that everyone learns the hard way: back up ~/vibeqc-runs/ like you back up everything else. The input scripts cost minutes to rewrite; the converged calculations cost hours-to-days. Keep them.

A throwaway one-liner for nightly tarball + offsite rsync:

tar czf ~/backup/vibeqc-runs-$(date +%Y%m%d).tar.gz ~/vibeqc-runs
rsync -av ~/backup/ user@backup-host:~/vibeqc-backup/

Or use whatever your group’s existing backup story is — but have one.