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:
Banner — vibe-qc + libint + libxc + spglib versions, for provenance. Identical to
vibeqc.print_banner().Job header — method + basis.
Initial atom table — Z + Cartesian bohr + charge + multiplicity
electron count.
Optimization block (only when
optimize=True) — target convergence, final optimized geometry.SCF trace — iteration-by-iteration energy, ΔE, commutator norm, DIIS history length. Flags
converged/NOT converged.Energy components (DFT only) — nuclear repulsion, electronic, Coulomb J, HF-exchange K, XC, total.
Orbital-energy table — all occupied MOs and up to
n_virtual=5virtual MOs (override vian_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 (
Afor 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’sm = -L..+Lconvention 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 |
1 |
banner + warnings + final SCF status only |
2 |
add per-stage milestones + |
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 |
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? |
|---|---|---|
|
Python |
yes |
|
Python |
yes |
|
Python |
yes |
|
Python |
yes |
|
Python |
yes |
|
C++ |
banner + post-hoc summary only |
|
C++ |
banner + post-hoc summary only |
|
wraps C++ |
banner + line-buffered |
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
PerfScopeopened during the run, sorted by wall-time. Theparcolumn 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? |
|---|---|---|
|
wraps everything inside |
yes |
|
ASE BFGS |
yes |
|
libint |
yes |
|
C++ molecular SCF (one row total) |
one row total |
|
molden writer |
yes |
|
Python lattice integrals |
yes |
|
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 |
|---|---|---|
|
|
first record, carries linked-library versions + run_fingerprint |
|
|
after method resolution, before any work |
|
|
after the memory pre-flight |
|
C++ molecular SCF (replayed from |
per SCF iteration |
|
|
once after the SCF loop |
|
|
post-SCF, when properties succeed; carries Mulliken / Löwdin / dipole |
|
|
when the C++ SCF raises; the exception still propagates after the dump |
|
|
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) |
|
SCF returns non-converged (max_iter exceeded) |
|
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 |
|---|---|---|
|
required |
|
|
required |
libint-recognized basis name |
|
|
|
|
|
XC functional name for RKS/UKS (e.g. |
|
|
path stem; files become |
|
|
run BFGS (via ASE) before the final SCF |
|
|
optimizer convergence in eV/Å (ASE convention) |
|
|
optimizer iteration limit |
|
|
emit the .molden file |
|
|
live progress logger; |
|
|
integer 0..9 (PySCF convention) gating how much live detail emits; 0 silent, 4 default with per-iter rows, 5 adds memory snapshots; |
|
|
route progress through |
|
|
post-mortem perf breakdown; |
|
|
machine-readable NDJSON; |
|
|
post-mortem dump on SCF failure ( |
|
|
record live hostname in |
|
|
fine SCF control — override the relevant options struct |
method="auto" resolves to:
functionalset + multiplicity 1 → RKSfunctionalset + multiplicity ≥ 2 → UKSno
functional+ multiplicity 1 → RHFno
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.