Input scripts and output files

A classic quantum-chemistry workflow means running a job and getting back a text log, plus files a viewer can open. vibeqc.run_job bundles that up so you don’t have to wire it together by hand: one call writes the formatted output, the molden orbital file, and — for optimization runs — a trajectory animation.

Writing an input script

An input “script” in vibe-qc is just a Python file. The conventional shape:

# input-h2o.py
from pathlib import Path
from vibeqc import Molecule, run_job

HERE = Path(__file__).parent

mol = Molecule.from_xyz(HERE / "h2o.xyz")

run_job(
    mol,
    basis="6-31g*",
    method="rhf",
    output=HERE / "output-h2o",
)

Run it like any Python file:

python3 input-h2o.py

Four ready-to-run examples live under examples/ — single-point RHF, DFT, open-shell UHF, and BFGS geometry optimization.

Output files

Given output="output-h2o", run_job writes up to seven file families named by the same stem (the .traj is geometry- optimization-only; .perf is opt-in via perf_log=; .scf.jsonl is opt-in via structured_log=; .dump only appears on SCF failure):

output-h2o.out — the text log

Plain ASCII, readable in any editor. Sections, in order:

  1. Banner — vibe-qc + libint + libxc + spglib versions, for provenance. Identical to vibeqc.print_banner().

  2. Job header — method + basis.

  3. Initial atom table — Z + Cartesian bohr + charge + multiplicity

    • electron count.

  4. Optimization block (only when optimize=True) — target convergence, final optimized geometry.

  5. SCF trace — iteration-by-iteration energy, ΔE, commutator norm, DIIS history length. Flags converged / NOT converged.

  6. Energy components (DFT only) — nuclear repulsion, electronic, Coulomb J, HF-exchange K, XC, total.

  7. Orbital-energy table — all occupied MOs and up to n_virtual=5 virtual MOs (override via n_virtual=...), with HOMO/LUMO markers and the HOMO-LUMO gap in Ha and eV. For UHF/UKS, separate Alpha and Beta blocks with per-spin HOSMO / LUSMO markers and per-spin gaps.

The same content is available programmatically as vibeqc.format_scf_trace(result, molecule=...) — pass it a file handle and a molecule to get a string you can log, print, or splice into your own output layout.

output-h2o.molden — molecular orbitals

Molden-format file carrying:

  • The geometry in atomic units.

  • The full basis set (per-atom, per-shell exponents and contraction coefficients, raw — primitive normalisation is reapplied by the reader).

  • Every molecular orbital: symmetry label (A for now, vibe-qc is not yet symmetry-adapted), orbital energy in Hartree, spin, occupancy (2 for occupied restricted, 1 for each alpha/beta spin-occupied, 0 otherwise), then the AO coefficients reordered from libint’s m = -L..+L convention to Molden’s (m=0, +1, -1, +2, -2, ...) ordering so the file is drop-in for any molden-aware viewer (Jmol, Avogadro, Molden itself, IQmol, MolView).

For unrestricted (UHF/UKS) results the file contains two MO blocks — first the Alpha spin, then the Beta spin — as the Molden format requires.

Open any .molden file via:

# Jmol (cross-platform, Java):
jmol output-h2o.molden

# Avogadro 2 (cross-platform):
avogadro output-h2o.molden

# MolTUI — terminal-only, no GUI required (great over SSH).
# Install via `pip install 'vibe-qc[viewer]'` or
# `./scripts/install_optional_tools.sh`. See the
# [installation page](../installation.md#optional-terminal-viewer-moltui).
moltui output-h2o.molden

output-h2o.system — runtime manifest

Plain TOML pinning the runtime environment that produced the .out. The .out file carries the chemistry; the .system sibling carries the hardware, linked-library, and timestamp context needed to interpret a wall-time figure or reproduce a calculation on a different box. Without it, an .out says “SCF total: 0.015 s” with no indication whether that’s a fast machine, a slow one, or a single-threaded build.

Sample manifest:

# vibe-qc system manifest — written alongside output-<job>.out by run_job(...).
# Captures the runtime environment so bundled reference outputs are
# reproducible and wall-time numbers are interpretable.

[vibeqc]
version       = "0.5.1"
codename      = "Wilson's Otter"
git_sha       = "c90398f"
git_branch    = "main"
is_release    = true                 # tag exactly at HEAD AND clean tree

[host]
hostname      = "peintinger-m2.local"
os            = "Darwin"
os_release    = "23.4.0"
os_pretty     = "macOS 14.4"
arch          = "arm64"

[cpu]
model         = "Apple M2 Pro"
physical_cores = 10
logical_cores  = 16
omp_threads_used = 12

[memory]
total_gb      = 32.0
available_gb  = 24.0

[python]
version       = "3.14.0"
implementation = "CPython"
executable    = "/Users/mpei/.venv/bin/python"

[libraries]
libint    = "2.13.1"
libxc     = "7.0.0"
spglib    = "2.7.0"
libecpint = "1.0.7"

[run]
timestamp_iso  = "2026-04-29T21:42:14-04:00"
wall_seconds   = 0.084
basename       = "input-h2o-rhf"

The shape is fixed — every section + key listed above is always present, even when a probe falls back to "unknown". Downstream parsers don’t need to handle missing keys, only the unknown sentinel value.

Privacy: hostname opt-out. For runs you plan to share publicly, pass record_hostname=False to run_job, or set the VIBEQC_NO_HOSTNAME=1 environment variable to opt out globally. Either lever emits hostname = "<redacted>" (the field stays present so the TOML shape is stable for parsers — only the value is masked). Engineering’s bundled docs runs use the env var so the docs/_static/examples/<slug>/<slug>.system files don’t leak machine names. Other manifest fields (CPU model, OS, memory, library versions) are not redacted — the redaction is scoped to the hostname only.

Read it from Python with stdlib tomllib:

import tomllib
with open("output-h2o.system", "rb") as f:
    manifest = tomllib.load(f)
print(manifest["cpu"]["model"], manifest["run"]["wall_seconds"])

Tutorial 29 walks through comparing your run’s manifest to the bundled reference for the same example.

output-h2o.traj — optimization trajectory

Emitted only when optimize=True. It’s an ASE binary trajectory — one frame per optimizer step, containing atomic positions + energy + forces. View it as an animation with:

ase gui output-h2o.traj

Convert to XYZ for tools that prefer that format:

ase convert output-h2o.traj output-h2o-frames.xyz

Or iterate frames programmatically:

from ase.io import read
frames = read("output-h2o.traj", index=":")
for step, atoms in enumerate(frames):
    print(step, atoms.get_potential_energy())

Progress logging

Long calculations — multi-minute molecular SCFs on a big basis, periodic bulk runs with EWALD_3D and a multi-k mesh — used to be silent until the SCF returned. The headline question this answers: is the calculation stuck or actually running?

run_job defaults to live progress on. The job emits a banner, per-stage milestones, and a final summary to stdout (line-flushed), and the .out file is line-buffered, so the canonical remote-job workflow shows progress in real time without any extra setup:

nohup python LiH.py > LiH.log 2>&1 &
tail -f LiH.log              # mirrors progress to the captured log
tail -f output-LiH.out       # same picture from the .out file directly

This emits, in order:

  • a banner naming the method, basis, functional, and thread count;

  • one line per setup stage (geometry_optimization, write_molden, …) with elapsed wall-time on completion;

  • the SCF banner (“Starting molecular SCF (RHF) …”);

  • the full SCF trace and orbital tables when the SCF returns;

  • a Job total X.XXs output written to summary line.

Disabling

Two equivalent levers — both restore the historical silent behavior:

# Per-call:
run_job(mol, basis="6-31g*", method="rhf", output="x",
        progress=False)
# Globally for a shell / batch job (only takes effect when
# `progress` is left at its default; explicit `progress=` kwargs win):
export VIBEQC_LIVE_LOGGING=0

A VIBEQC_LIVE_LOGGING=0 env var is the right answer for batch scripts that don’t want to edit every input file. Explicit progress=True / progress=False / a ProgressLogger instance always wins, so a debugging session can re-enable progress for one shell.

Verbosity

The verbose= kwarg on run_job (added in v0.5.3) tunes how much detail the live progress + .out carry. Levels follow the PySCF convention — each level is a strict superset of the one below, so bumping verbose only adds output:

level

what is emitted

0

silent — nothing live (the .out file is still written)

1

banner + warnings + final SCF status only

2

add per-stage milestones + info() lines

3

add per-stage timing on stage exit

4

default — add per-iteration SCF rows

5

add inline RSS-memory snapshots

6+

phase-level wall-clock breakdown live (overlaps the post-mortem .perf log)

Two equivalent ways to set the level:

# Per-call:
run_job(mol, basis="6-31g*", method="rhf", output="x",
        verbose=2)
# Globally for a shell / batch job (only takes effect when
# `verbose` is left at its default of None; explicit `verbose=`
# kwargs win):
export VIBEQC_VERBOSE=2

A junk env value (typo, leftover) silently falls back to the package default — an overnight batch shouldn’t die because of VIBEQC_VERBOSE=verbose. Levels 1 and 2 are the right knob for batch sweeps that want one summary per job without per-iter spam; level 5+ is for debugging a specific run that’s behaving oddly. The level only gates the live emit — the format_scf_trace block in {output}.out is unaffected and always carries the full per-iteration history.

Stdlib logging integration

When a project already pipes everything through logging (rotating files, syslog, JSON-to-Loki, dictConfig), the use_logging=True kwarg routes vibe-qc’s progress through the same stack instead of bare stdout writes:

import logging
import vibeqc as vq

logging.basicConfig(level=logging.INFO)
vq.run_job(mol, basis="6-31g*", method="rhf", output="x",
           use_logging=True)

Banners, milestones, and the final SCF summary land at INFO; per-iteration SCF rows at DEBUG; warnings at WARNING. The logger name is vibeqc.run_job, so a dictConfig block can target it specifically:

logging.config.dictConfig({
    "version": 1,
    "handlers": {
        "scf_log": {
            "class": "logging.handlers.RotatingFileHandler",
            "filename": "scf.log", "maxBytes": 10_000_000,
            "backupCount": 5,
        },
    },
    "loggers": {
        "vibeqc.run_job": {"handlers": ["scf_log"], "level": "INFO"},
    },
})

The verbose-level gate runs before the logging call — so verbose=2 + use_logging=True does not emit per-iter DEBUG records, even if the active handler is set to DEBUG. progress=False still wins as a hard kill switch, so a silent run stays silent regardless of the active logging config.

Routing — ProgressLogger

For finer control — tee the same trace to a persistent file, or thread a single logger through nested calls — instantiate a vibeqc.ProgressLogger directly:

import vibeqc as vq

plog = vq.ProgressLogger(log_path="lih.progress.log", verbose=True)
vq.run_rhf_periodic_scf(system, basis, kpoints, opts, progress=plog)

The same progress= kwarg is accepted by every periodic SCF entry point (run_rhf_periodic_scf, run_rks_periodic_scf, the EWALD_3D variants, etc.) — pass True to get stdout, or a logger for routing.

Coverage

Live per-iteration progress is available everywhere the SCF loop runs in Python — which is every EWALD_3D path, including the heavy multi-k bulk runs that motivated the feature:

Entry point

Backend

Live per-iter?

run_rhf_periodic_scf (EWALD_3D)

Python

yes

run_rhf_periodic_gamma_scf (EWALD_3D)

Python

yes

run_rks_periodic_scf / run_rks_periodic_gamma_scf (EWALD_3D)

Python

yes

run_uhf_periodic_*_ewald3d

Python

yes

run_uks_periodic_*_ewald3d

Python

yes

*_periodic_*_scf (DIRECT_TRUNCATED)

C++

banner + post-hoc summary only

run_rhf / run_uhf / run_rks / run_uks (molecular)

C++

banner + post-hoc summary only

run_job (molecular)

wraps C++

banner + line-buffered .out + post-hoc summary

The C++-driven SCFs don’t expose a Python progress callback today, so the per-iteration trace is collected into result.scf_trace and written to the .out file when the SCF returns. The pre-SCF banner and post-SCF summary still emit live, so a remote-job operator at least sees that the calculation is alive.

Performance debugging

The post-mortem companion to live progress logging. Live logging shows progress during a run; the perf log shows where the time went afterwards. The two pair: live for “is the SCF stuck?”, perf for “why is my LiH / pob-TZVP run taking 20 minutes — is it J, K, XC quadrature, or Bloch sums?”.

run_job writes a {output}.perf sibling when you pass perf_log=True:

run_job(mol, basis="cc-pVDZ", method="rks", functional="pbe",
        output="output-h2o", perf_log=True)
# -> output-h2o.out / .molden / .system / .perf

The file is plain text, sortable by total wall-time. A typical report:

========================================================================
vibe-qc performance / debug log
========================================================================
  Total wall time:   19.599 s
  OMP threads:     12
  Phases tracked:  4

Phase summary  (sorted by total wall time, descending)
------------------------------------------------------------------------
  phase                                 n         wall          cpu  % wall    par
  ----------------------------------------------------------------------
  periodic.integrals_lattice            1      4.68 ms      9.86 ms    0.0%   0.18
  periodic.compute_nuclear_lattice      1      4.17 ms      8.82 ms    0.0%   0.18
  periodic.compute_overlap_lattice      1      0.30 ms      0.64 ms    0.0%   0.18
  periodic.compute_kinetic_lattice      1      0.16 ms      0.35 ms    0.0%   0.18

Memory snapshots  (RSS in MiB)
------------------------------------------------------------------------
  label                                  t (s)     RSS (MiB)
  ------------------------------------------------------------
  start_of_scf                           0.013         149.3
  end_of_scf                            19.598         152.1

SCF iterations
------------------------------------------------------------------------
  iter              E (Ha)           dE   ||[F,DS]||  DIIS   wall (s)
  ----------------------------------------------------------------------
     1      -28.9250484403        --     4.437e-02     -      3.895
     2      -28.9261305496  -1.082e-03  3.304e-02     1      7.747
     ...
========================================================================

Sections, in order:

  • Header — total wall, OMP thread count, phase count.

  • Phase summary — one row per PerfScope opened during the run, sorted by wall-time. The par column is parallelism = CPU time / (wall time × threads); 1.0 means perfect OpenMP scaling, < 0.7 flags an under-parallelised hot path.

  • Under-parallelised hot paths — auto-flag block listing any phase that consumed more than 5% of wall time and ran at parallelism < 0.7×. The under-parallelised hot paths users care about.

  • Memory snapshots — labeled RSS samples (start_of_scf, end_of_scf, …) so you can see RSS growth caused by the Fock build separate from the basis-set / pre-flight overhead.

  • SCF iterations — per-iteration table (energy, ΔE, ‖[F,DS]‖, DIIS subspace, wall-time-since-SCF-start). The post-mortem analogue of live progress logging’s per-iter emission.

Three ways to enable

# (1) Env var — common case for one-off jobs:
VIBEQC_PERFLOG=output.perf python my-calc.py
# (2) Programmatic context manager — wrap a region:
import vibeqc as vq

with vq.perf_log("output.perf"):
    result = vq.run_rhf(mol, basis, opts)
    hess = vq.compute_hessian_rhf_analytic(...)
# All work inside the block accumulates into the same tracker;
# the report is written when the block exits.

# (3) run_job kwarg — one-shot:
vq.run_job(mol, basis="cc-pVDZ", method="rks", functional="pbe",
           output="x", perf_log="x.perf")  # explicit path
vq.run_job(mol, basis="cc-pVDZ", method="rks", functional="pbe",
           output="x", perf_log=True)      # → x.perf sibling

The three knobs feed the same vibeqc.PerfTracker accumulator. Explicit perf_log= always wins over the env var; off by default.

Reading the report programmatically

The tracker is a plain Python object — call sites that want to script around the perf data can read it directly:

import vibeqc as vq

with vq.perf_log() as tracker:
    vq.run_rhf(mol, basis, opts)

for phase in sorted(tracker.phases.values(),
                    key=lambda p: -p.wall_s):
    print(f"{phase.name}: {phase.wall_s:.3f}s "
          f"({phase.n_calls} calls)")

tracker.phases, tracker.scf_iters, and tracker.memory_snapshots are public attributes — full API in vibeqc.PerfTracker.

Coverage

What’s instrumented today:

Phase

Driver

Live perf rows?

run_job.total

wraps everything inside run_job

yes

geometry_optimization

ASE BFGS

yes

basis_set_construction

libint

yes

scf.{rhf,uhf,rks,uks}

C++ molecular SCF (one row total)

one row total

write_molden

molden writer

yes

periodic.integrals_lattice (+ S/T/V sub-scopes)

Python lattice integrals

yes

periodic.*_periodic_*_ewald3d SCF iteration loop

Python

per-iter wall in SCF table

The C++ kernels (compute_eri, build_coulomb, build_exchange, xc_eval, diag_k, s_inverse_sqrt_complex, bloch_sum) are not instrumented yet — those scopes live in C++ and need a compile-time #ifdef VIBEQC_PERFLOG hook to keep release builds zero-cost. They arrive in a v0.5.2.x patch.

Structured machine-readable log

A third observability surface (after the human-readable .out and the post-mortem .perf): one JSON record per SCF transition, written line-flushed to {output}.scf.jsonl so dashboards and analysis scripts can ingest convergence data without screen- scraping the text log.

Format: NDJSON (one JSON object per line, no enclosing array). Every record carries "event" plus a per-event payload. Event names + field names are append-only — never renamed or removed — so v0.6 callers stay forward-compatible with v0.7+ additions.

A typical sequence for a successful molecular SCF (one JSON record per line — the format Pygments calls text, not jsonl):

{"event":"banner","timestamp":"...","vibeqc_version":"0.6.0.dev0","libint":"2.13.1","libxc":"7.0.0","spglib":"2.7.0","run_fingerprint":"abc1234567890abc"}
{"event":"job_start","timestamp":"...","method":"rhf","basis":"sto-3g","functional":null,"optimize":false,"threads":12,"n_atoms":2,"charge":0,"multiplicity":1,"n_electrons":2,"output_stem":"h2"}
{"event":"memory_estimate","timestamp":"...","total_gb":1.16e-06,"raw_total_bytes":1040,"headroom_factor":1.2,"by_category":{"ERI tensor":128,"Fock + density + 1e":256,"DIIS history":512,"MO workspace":144}}
{"event":"scf_iter","timestamp":"...","iter":1,"energy":-0.71,"dE":null,"grad_norm":4.7e-16,"diis_subspace":1}
{"event":"scf_iter","timestamp":"...","iter":2,"energy":-1.117,"dE":-0.398,"grad_norm":0.0,"diis_subspace":2}
{"event":"scf_converged","timestamp":"...","n_iter":3,"energy":-1.1167143251,"converged":true}
{"event":"properties","timestamp":"...","mulliken":[6.7e-16,-4.4e-16],"loewdin":[7.8e-16,0.0],"dipole":{"x":0.0,"y":0.0,"z":-7.8e-16,"total":7.8e-16,"total_debye":2.0e-15}}
{"event":"job_end","timestamp":"...","total_wall_s":0.099,"scf_wall_s":0.004,"opt_wall_s":0.0,"n_iter":3,"converged":true,"energy":-1.1167143251,"out_path":"h2.out"}

Strict JSON: NaN / ±Infinity are coerced to null so the file parses cleanly with jq, jq -c '.', and any other strict-JSON tool. The first iteration’s dE is null (not 0) — same placeholder semantics as the human-readable trace.

The run_fingerprint field is a 16-character hex digest of the identifying inputs (method + basis + functional + atoms + charge

  • multiplicity). Two runs with the same fingerprint are calculating the same thing — useful for “did this run change?” checks in CI dashboards.

Three ways to enable

# (1) Env var — common case for one-off jobs:
VIBEQC_STRUCTURED_LOG=output.scf.jsonl python my-calc.py
# (2) Programmatic context manager — wrap a region:
import vibeqc as vq

with vq.structured_log("output.scf.jsonl"):
    vq.run_rhf(mol, basis, opts)
# All work inside the block emits to the same file; periodic SCFs
# emit per-iter rows live via the same context-var funnel that
# vibeqc.ProgressLogger uses.

# (3) run_job kwarg — one-shot:
vq.run_job(mol, basis="cc-pVDZ", method="rks", functional="pbe",
           output="x", structured_log=True)        # → x.scf.jsonl
vq.run_job(mol, basis="cc-pVDZ", method="rks", functional="pbe",
           output="x", structured_log="other.jsonl")  # explicit

Off by default — run_job only writes the file when the caller opts in. Explicit structured_log= always wins over the env var.

Tail-friendly

The file is line-flushed: a tail -f output.scf.jsonl shows records as they’re emitted (one per SCF iteration during the SCF, plus banner / properties / job_end at boundaries). Pair with jq for a live convergence monitor:

tail -f output.scf.jsonl | jq -c 'select(.event == "scf_iter") | [.iter, .energy, .dE, .grad_norm]'

Coverage

Event

Source

When emitted

banner

run_job

first record, carries linked-library versions + run_fingerprint

job_start

run_job

after method resolution, before any work

memory_estimate

run_job

after the memory pre-flight

scf_iter

C++ molecular SCF (replayed from result.scf_trace) + Python periodic SCFs (live via ProgressLogger.iteration)

per SCF iteration

scf_converged

ProgressLogger.converged funnel

once after the SCF loop

properties

run_job

post-SCF, when properties succeed; carries Mulliken / Löwdin / dipole

scf_failed

run_job

when the C++ SCF raises; the exception still propagates after the dump

job_end

run_job

last record, total_wall_s + out_path

Crash dumps

When an SCF fails ungracefully — raised exception (NaN in the density, severe linear dependence, OOM) or runs to max_iter without converging — run_job writes a snapshot to {output}.dump plus binary attachments. Three-line bug report: attach output.dump + output.dump.density.npy + the input script and the maintainer can reconstruct the exact failing state via vibeqc.load_dump.

The dump is on by default: post-mortem reproducibility costs zero bytes on success and saves a re-run on failure. Disable per-call with crash_dump=False or globally with VIBEQC_NO_CRASH_DUMP=1 in the environment.

File layout

For output="output-h2o" and a NaN failure at SCF iteration 5:

  • output-h2o.dump — TOML with [crash], [scf.last_iter], [geometry], [molecule], [options], [hint], and [attachments] sections.

  • output-h2o.dump.density.npy — last-iteration density matrix.

  • output-h2o.dump.fock.npy — last-iteration Fock matrix (when present).

  • output-h2o.dump.mo.npy — current MO coefficients (when present).

A typical .dump body (real .dump files are TOML; the example below is rendered as plain text because the illustrative ... and nan placeholders below aren’t valid TOML literals on their own):

[crash]
when = "2026-04-30T20:27:26-05:00"
phase = "scf_iteration_5"
exception_type = "RuntimeError"
exception = "NaN in density matrix"
n_iters_completed = 4

[scf.last_iter]
iter = 4
energy = -74.123
delta_e = nan
grad_norm = 1.7e+02
diis_subspace = 4

[geometry]
atoms = [
  { Z = 8, x = 0.0, y = 0.0, z = 0.0 },
  ...
]

[molecule]
charge = 0
multiplicity = 1
n_atoms = 3

[options]
max_iter = 100
damping = 0.0
...

[hint]
likely_cause = "DIIS instability — try damping=0.5, level_shift=0.5, or DIIS=False to fall back to plain Roothaan iterations."

[attachments]
files = "output-h2o.dump.density.npy, output-h2o.dump.fock.npy, output-h2o.dump.mo.npy"

The [hint] block runs a small heuristic against the exception text + type to produce a one-line likely_cause. It’s best-effort and never blocks the dump if the keyword search doesn’t match anything specific.

Reproducer recipe

import vibeqc as vq

dump = vq.load_dump("output-h2o.dump")
density = dump["arrays"]["density"]   # numpy ndarray, last iteration
options = dump["options"]              # dict, ready to feed back in
print(dump["crash"]["phase"], "→", dump["hint"]["likely_cause"])

vibeqc.load_dump returns a nested dict shaped like the TOML sections, plus an extra "arrays" key carrying every sibling .dump.<name>.npy rebuilt with numpy.load. Pair with the input script in the bug report and the maintainer reconstructs the failing state bit-for-bit.

Failure modes that trigger a dump

Failure

Where the dump fires

C++ SCF raises (NaN, lin-dep, memory error)

run_job’s try/except wraps the SCF call → dump + re-raise

SCF returns non-converged (max_iter exceeded)

run_job checks result.converged after success; dumps if False, returns the result normally (does NOT raise)

Pre-SCF errors (basis-set construction, memory pre-flight abort)

not currently captured — the .out file holds the error message; a future patch will widen the dump scope

The exception path always re-raises — crash_dump=True does NOT swallow failures; it just makes them debuggable. The max-iter path returns the non-converged result so callers that explicitly want to inspect a failed iterate keep working.

run_job parameters

Parameter

Default

Purpose

molecule

required

Molecule in bohr coordinates

basis=

required

libint-recognized basis name

method=

"auto"

"rhf" / "uhf" / "rks" / "uks" / "auto"

functional=

None

XC functional name for RKS/UKS (e.g. "PBE", "B3LYP")

output=

"output"

path stem; files become {output}.out, {output}.molden, {output}.traj

optimize=

False

run BFGS (via ASE) before the final SCF

fmax=

0.05

optimizer convergence in eV/Å (ASE convention)

max_opt_steps=

200

optimizer iteration limit

write_molden_file=

True

emit the .molden file

progress=

None (resolves to live-on)

live progress logger; False to silence stdout, a ProgressLogger to route, or set VIBEQC_LIVE_LOGGING=0 globally for batch scripts

verbose=

None (resolves to 4)

integer 0..9 (PySCF convention) gating how much live detail emits; 0 silent, 4 default with per-iter rows, 5 adds memory snapshots; None defers to VIBEQC_VERBOSE=N env var

use_logging=

False

route progress through logging.getLogger("vibeqc.run_job") instead of bare stdout; composes with stdlib RotatingFileHandler / syslog / dictConfig

perf_log=

None

post-mortem perf breakdown; True writes {output}.perf, a path writes there explicitly, None defers to VIBEQC_PERFLOG=path env var

structured_log=

False

machine-readable NDJSON; True writes {output}.scf.jsonl, a path writes there explicitly, None/False defers to VIBEQC_STRUCTURED_LOG=path env var

crash_dump=

True

post-mortem dump on SCF failure ({output}.dump + .dump.density.npy etc.); pass False (or set VIBEQC_NO_CRASH_DUMP=1) to disable

record_hostname=

True

record live hostname in {output}.system; pass False (or set VIBEQC_NO_HOSTNAME=1) to emit hostname = "<redacted>"

rhf_options= / uhf_options= / rks_options= / uks_options=

None

fine SCF control — override the relevant options struct

method="auto" resolves to:

  • functional set + multiplicity 1 → RKS

  • functional set + multiplicity ≥ 2 → UKS

  • no functional + multiplicity 1 → RHF

  • no functional + multiplicity ≥ 2 → UHF

The return value is the underlying SCF result object (RHFResult, UHFResult, RKSResult, or UKSResult) — so you can continue in Python after the call to inspect MO coefficients, density matrices, or feed the result to downstream post-SCF analysis.

When to use run_job vs the low-level drivers

run_job optimises for the 80% case: one method on one geometry, producing a log and an orbital file. If you want to

  • sweep over basis sets / functionals in one script,

  • compose your own output format,

  • run periodic systems (not yet wired into run_job),

  • or call the SCF drivers with non-default numerical integration grids,

reach for the low-level API (run_rhf, run_rks, etc.) and format_scf_trace directly. Everything run_job does internally is re-usable via the same public API.