Logging & output#

Every run in this guide has quietly produced a transcript — the system summary, the SCF cycle table, the convergence check. This chapter is about seeing, replaying, and saving that output: how to stream it live, render it after the fact, get it as machine-readable data, and persist a whole calculation to disk.

One event stream behind everything#

qc-rs’s logging and display share a single structured event stream. As a run proceeds it emits typed events — a System summary, a plan, per-cycle SCF records, a result — and every way of viewing output is just a different rendering of that one stream. That is why the live log, the replayed transcript, and the raw JSON all show the same information: they are the same events, formatted differently. Because it is structured (not free-text prints), you can render it as a human transcript or consume it as data.

Live logging: run(log=...)#

.run() is silent by default — it computes and returns. Pass log= to stream the transcript as the run happens (introduced in the quickstart):

import qc
water = "O 0 0 0.117; H 0 0.757 -0.469; H 0 -0.757 -0.469"

m = qc.chk.new(atom=water, ao="cc-pvdz", unit="angstrom").scf(ref="r").run(log="stdout")

The log= target can be:

log=

effect

"stdout"

stream to the terminal / notebook cell as the run proceeds

a file path

write the rendered transcript to that file

a file-like object

write to any stream you pass

Two more controls shape the live output:

  • log_style="modern" (default) or "orca" — the visual style of the transcript (a compact modern layout, or one reminiscent of ORCA output).

  • plot=True — draw the SCF convergence plot inline as it runs (needs matplotlib; see visualization).

Tip

MPI runs On a parallel run, log_rank="root" (default) logs only rank 0; "all", "gather", or a list of ranks control which ranks report. This keeps a many-rank run from flooding the output. Parallelism itself is Part IV.

Replaying a finished run: log() and show()#

The transcript is stored on the checkpoint, so you can render it after the run without recomputing:

m.log()                    # replay the transcript as text
m.log(format="markdown")   # ... as Markdown (nice in a notebook)
m.log(format="jsonl")      # ... as one JSON object per line
m.show("result")           # a rendered snapshot of the current state + results
  • log(format=...) re-renders the stored event stream — "text" (default), "markdown", or "jsonl".

  • show(...) renders a snapshot (state and results) rather than the running transcript — useful for a quick “what does this checkpoint hold right now” view.

Because these read the stored events, they cost nothing to call and give the identical transcript every time — no need to re-run with log="stdout" just to see the output again.

Output as data: run_events()#

For programmatic use — testing, dashboards, harvesting numbers — run_events() returns the raw event stream as a list of JSON strings, one per event:

import json
events = m.run_events()             # list[str], each a JSON object
len(events)                         # 18

first = json.loads(events[0])       # the System summary event
first["event"]                      # e.g. "system"
first["nao"], first["nuclear_repulsion"]   # structured fields, ready to use
[json.loads(e).get("kind") for e in events]   # e.g. the auto-inserted 'sad' guess shows up here

Each event is a dict with an event field naming its type and the structured payload for that type (atom list, basis size, per-cycle energies, …). This is the stream the human transcript is rendered from, so nothing shown in the log is hidden from the data.

Saving and loading a calculation#

A checkpoint — molecule, current electronic state, results, and transcript — persists to an HDF5 .qch5 file, and reloads into a checkpoint you can query or extend:

m.save("water.qch5")                 # persist the whole checkpoint
r = qc.chk.load("water.qch5")        # restore it

r.scf.energy                         # -76.026794   results survive the round-trip
r.scf(xc="b3lyp").run()              # ... and you can add more steps to the loaded checkpoint

save()/load() are how you stop and resume work: run an expensive SCF once, save it, and later load it to compute properties, optimize, or restart — no recomputation. (This is also the file behind guess("read", source=...) from the initial-guess chapter.)

Worked example: run quietly, inspect afterwards#

import qc, json
water = "O 0 0 0.117; H 0 0.757 -0.469; H 0 -0.757 -0.469"

m = qc.chk.new(atom=water, ao="cc-pvdz", unit="angstrom").scf(ref="r").run()   # silent

m.log()                              # ... now render the transcript
n_events = len(m.run_events())       # 18 structured events available as data
m.save("water.qch5")                 # keep it for next time
print("events:", n_events, "| E:", round(m.scf.energy, 6))

Exercise 9

  1. You ran m = chk.scf(ref="r").run() (no log=) and now want to see the SCF cycle table without paying for another SCF. What do you call?

  2. You need the per-cycle energies in a Python script to make your own plot. Which method gives you the data, and in what form?

  3. An expensive optimization finished. How do you make sure you never have to repeat it, and how would you later reuse its converged orbitals as the starting guess for a bigger-basis run?

That completes the day-to-day workflow. The rest of Part III is the large molecular-properties suite — turning a converged wavefunction into charges, bond orders, topological analysis, and the real-space fields you met in visualization.